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
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex configuration management.
|
|
3
|
+
*/
|
|
4
|
+
/** Get the codex home directory */
|
|
5
|
+
declare function getCodexHome(): string;
|
|
6
|
+
/** Get auth file path */
|
|
7
|
+
declare function getAuthFilePath(): string;
|
|
8
|
+
/** Compute the keychain key for Codex (matches Rust implementation) */
|
|
9
|
+
declare function computeKeychainKey(codexHome: string): string;
|
|
10
|
+
/** Update Codex config.toml to set credential storage type */
|
|
11
|
+
declare function updateConfigStorage(useKeychain: boolean): void;
|
|
12
|
+
export { computeKeychainKey, getAuthFilePath, getCodexHome, updateConfigStorage, };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex configuration management.
|
|
3
|
+
*/
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { realpathSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { codexConfigReader } from "axconfig";
|
|
8
|
+
import { getResolvedConfigDirectory } from "../resolve-config-directory.js";
|
|
9
|
+
/** Get the codex home directory */
|
|
10
|
+
function getCodexHome() {
|
|
11
|
+
return getResolvedConfigDirectory(codexConfigReader);
|
|
12
|
+
}
|
|
13
|
+
/** Get auth file path */
|
|
14
|
+
function getAuthFilePath() {
|
|
15
|
+
return path.join(getCodexHome(), "auth.json");
|
|
16
|
+
}
|
|
17
|
+
/** Compute the keychain key for Codex (matches Rust implementation) */
|
|
18
|
+
function computeKeychainKey(codexHome) {
|
|
19
|
+
let canonicalPath;
|
|
20
|
+
try {
|
|
21
|
+
canonicalPath = realpathSync(codexHome);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
canonicalPath = codexHome;
|
|
25
|
+
}
|
|
26
|
+
const hash = createHash("sha256").update(canonicalPath).digest("hex");
|
|
27
|
+
const truncated = hash.slice(0, 16);
|
|
28
|
+
return `cli|${truncated}`;
|
|
29
|
+
}
|
|
30
|
+
/** Update Codex config.toml to set credential storage type */
|
|
31
|
+
function updateConfigStorage(useKeychain) {
|
|
32
|
+
const configDirectory = getCodexHome();
|
|
33
|
+
if (useKeychain) {
|
|
34
|
+
codexConfigReader.writeRaw(configDirectory, "cli_auth_credentials_store", "keyring");
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
codexConfigReader.deleteRaw(configDirectory, "cli_auth_credentials_store");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export { computeKeychainKey, getAuthFilePath, getCodexHome, updateConfigStorage, };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex credential storage operations.
|
|
3
|
+
*/
|
|
4
|
+
interface CodexAuthJson {
|
|
5
|
+
OPENAI_API_KEY?: string | null;
|
|
6
|
+
tokens?: {
|
|
7
|
+
id_token?: string;
|
|
8
|
+
access_token?: string;
|
|
9
|
+
refresh_token?: string;
|
|
10
|
+
};
|
|
11
|
+
last_refresh?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Get API key from environment */
|
|
14
|
+
declare function getEnvironmentApiKey(): string | undefined;
|
|
15
|
+
/** Check if keychain has API key */
|
|
16
|
+
declare function hasKeychainApiKey(auth: CodexAuthJson | undefined): boolean;
|
|
17
|
+
/** Check if keychain has OAuth tokens */
|
|
18
|
+
declare function hasKeychainOAuth(auth: CodexAuthJson | undefined): boolean;
|
|
19
|
+
/** Check if file has API key */
|
|
20
|
+
declare function hasFileApiKey(auth: CodexAuthJson | undefined): boolean;
|
|
21
|
+
/** Check if file has OAuth tokens */
|
|
22
|
+
declare function hasFileOAuth(auth: CodexAuthJson | undefined): boolean;
|
|
23
|
+
/** Load credentials from keychain */
|
|
24
|
+
declare function loadKeychainCreds(): CodexAuthJson | undefined;
|
|
25
|
+
/** Save credentials to keychain */
|
|
26
|
+
declare function saveKeychainCreds(auth: CodexAuthJson): boolean;
|
|
27
|
+
/** Delete credentials from keychain */
|
|
28
|
+
declare function deleteKeychainCreds(): boolean;
|
|
29
|
+
/** Load credentials from file */
|
|
30
|
+
declare function loadFileCreds(): CodexAuthJson | undefined;
|
|
31
|
+
/** Save credentials to file */
|
|
32
|
+
declare function saveFileCreds(auth: CodexAuthJson, filePath?: string): boolean;
|
|
33
|
+
/** Delete credentials from file */
|
|
34
|
+
declare function deleteFileCreds(filePath?: string): boolean;
|
|
35
|
+
interface CredentialsData {
|
|
36
|
+
apiKey?: string;
|
|
37
|
+
_source?: unknown;
|
|
38
|
+
tokens?: CodexAuthJson["tokens"];
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
}
|
|
41
|
+
/** Build auth content from credentials data */
|
|
42
|
+
declare function buildAuthContent(type: "oauth" | "api-key", data: CredentialsData): CodexAuthJson;
|
|
43
|
+
export { buildAuthContent, deleteFileCreds, deleteKeychainCreds, getEnvironmentApiKey, hasFileApiKey, hasFileOAuth, hasKeychainApiKey, hasKeychainOAuth, loadFileCreds, loadKeychainCreds, saveFileCreds, saveKeychainCreds, };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex credential storage operations.
|
|
3
|
+
*/
|
|
4
|
+
import { deleteFile, loadJsonFile, saveJsonFile } from "../file-storage.js";
|
|
5
|
+
import { deleteFromKeychain, loadFromKeychain, saveToKeychain, } from "../keychain.js";
|
|
6
|
+
import { computeKeychainKey, getAuthFilePath, getCodexHome, } from "./codex-config.js";
|
|
7
|
+
const KEYCHAIN_SERVICE = "Codex Auth";
|
|
8
|
+
/** Get API key from environment */
|
|
9
|
+
function getEnvironmentApiKey() {
|
|
10
|
+
return process.env.CODEX_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
11
|
+
}
|
|
12
|
+
/** Check if keychain has API key */
|
|
13
|
+
function hasKeychainApiKey(auth) {
|
|
14
|
+
return Boolean(auth?.OPENAI_API_KEY);
|
|
15
|
+
}
|
|
16
|
+
/** Check if keychain has OAuth tokens */
|
|
17
|
+
function hasKeychainOAuth(auth) {
|
|
18
|
+
return Boolean(auth?.tokens?.access_token);
|
|
19
|
+
}
|
|
20
|
+
/** Check if file has API key */
|
|
21
|
+
function hasFileApiKey(auth) {
|
|
22
|
+
return Boolean(auth?.OPENAI_API_KEY);
|
|
23
|
+
}
|
|
24
|
+
/** Check if file has OAuth tokens */
|
|
25
|
+
function hasFileOAuth(auth) {
|
|
26
|
+
return Boolean(auth?.tokens?.access_token);
|
|
27
|
+
}
|
|
28
|
+
/** Load credentials from keychain */
|
|
29
|
+
function loadKeychainCreds() {
|
|
30
|
+
const key = computeKeychainKey(getCodexHome());
|
|
31
|
+
const raw = loadFromKeychain(KEYCHAIN_SERVICE, key);
|
|
32
|
+
if (!raw)
|
|
33
|
+
return undefined;
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Save credentials to keychain */
|
|
42
|
+
function saveKeychainCreds(auth) {
|
|
43
|
+
const key = computeKeychainKey(getCodexHome());
|
|
44
|
+
return saveToKeychain(KEYCHAIN_SERVICE, key, JSON.stringify(auth));
|
|
45
|
+
}
|
|
46
|
+
/** Delete credentials from keychain */
|
|
47
|
+
function deleteKeychainCreds() {
|
|
48
|
+
const key = computeKeychainKey(getCodexHome());
|
|
49
|
+
return deleteFromKeychain(KEYCHAIN_SERVICE, key);
|
|
50
|
+
}
|
|
51
|
+
/** Load credentials from file */
|
|
52
|
+
function loadFileCreds() {
|
|
53
|
+
return loadJsonFile(getAuthFilePath());
|
|
54
|
+
}
|
|
55
|
+
/** Save credentials to file */
|
|
56
|
+
function saveFileCreds(auth, filePath) {
|
|
57
|
+
return saveJsonFile(filePath ?? getAuthFilePath(), auth, { mode: 0o600 });
|
|
58
|
+
}
|
|
59
|
+
/** Delete credentials from file */
|
|
60
|
+
function deleteFileCreds(filePath) {
|
|
61
|
+
return deleteFile(filePath ?? getAuthFilePath());
|
|
62
|
+
}
|
|
63
|
+
/** Build auth content from credentials data */
|
|
64
|
+
function buildAuthContent(type, data) {
|
|
65
|
+
if (type === "api-key") {
|
|
66
|
+
return { OPENAI_API_KEY: data.apiKey };
|
|
67
|
+
}
|
|
68
|
+
const { _source: _unused, ...rest } = data;
|
|
69
|
+
void _unused;
|
|
70
|
+
if ("tokens" in rest) {
|
|
71
|
+
const auth = rest;
|
|
72
|
+
if (!auth.last_refresh) {
|
|
73
|
+
auth.last_refresh = new Date().toISOString();
|
|
74
|
+
}
|
|
75
|
+
return auth;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
tokens: rest,
|
|
79
|
+
last_refresh: new Date().toISOString(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export { buildAuthContent, deleteFileCreds, deleteKeychainCreds, getEnvironmentApiKey, hasFileApiKey, hasFileOAuth, hasKeychainApiKey, hasKeychainOAuth, loadFileCreds, loadKeychainCreds, saveFileCreds, saveKeychainCreds, };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex auth adapter.
|
|
3
|
+
*
|
|
4
|
+
* Supports API key and OAuth via keychain (macOS) or file.
|
|
5
|
+
*/
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { ensureDirectory } from "../file-storage.js";
|
|
8
|
+
import { isMacOS } from "../keychain.js";
|
|
9
|
+
import { checkAuth, extractRawCredentials } from "./codex-auth-check.js";
|
|
10
|
+
import { getAuthFilePath, getCodexHome, updateConfigStorage, } from "./codex-config.js";
|
|
11
|
+
import { buildAuthContent, deleteFileCreds, deleteKeychainCreds, saveFileCreds, saveKeychainCreds, } from "./codex-storage.js";
|
|
12
|
+
const AGENT_ID = "codex";
|
|
13
|
+
/** Codex authentication adapter */
|
|
14
|
+
const codexAdapter = {
|
|
15
|
+
agentId: AGENT_ID,
|
|
16
|
+
capabilities: {
|
|
17
|
+
keychain: true,
|
|
18
|
+
file: true,
|
|
19
|
+
environment: true,
|
|
20
|
+
installApiKey: true,
|
|
21
|
+
},
|
|
22
|
+
checkAuth,
|
|
23
|
+
extractRawCredentials,
|
|
24
|
+
installCredentials(creds, options) {
|
|
25
|
+
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)) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
message: `Failed to create directory ${parentDirectory}`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (saveFileCreds(authContent, options.path)) {
|
|
36
|
+
return {
|
|
37
|
+
ok: true,
|
|
38
|
+
message: `Installed credentials to ${options.path}`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
message: `Failed to install credentials to ${options.path}`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Default location: use storage option or _source marker
|
|
47
|
+
const sourceMarker = creds.data._source;
|
|
48
|
+
const targetStorage = options?.storage ?? (sourceMarker === "keychain" ? "keychain" : "file");
|
|
49
|
+
if (targetStorage === "keychain") {
|
|
50
|
+
if (!isMacOS()) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
message: "Keychain storage is only available on macOS",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (saveKeychainCreds(authContent)) {
|
|
57
|
+
updateConfigStorage(true);
|
|
58
|
+
return { ok: true, message: "Installed credentials to macOS Keychain" };
|
|
59
|
+
}
|
|
60
|
+
return { ok: false, message: "Failed to save to macOS Keychain" };
|
|
61
|
+
}
|
|
62
|
+
if (!ensureDirectory(getCodexHome())) {
|
|
63
|
+
return { ok: false, message: "Failed to create codex directory" };
|
|
64
|
+
}
|
|
65
|
+
if (saveFileCreds(authContent)) {
|
|
66
|
+
updateConfigStorage(false);
|
|
67
|
+
return {
|
|
68
|
+
ok: true,
|
|
69
|
+
message: `Installed credentials to ${getAuthFilePath()}`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { ok: false, message: "Failed to install credentials to file" };
|
|
73
|
+
},
|
|
74
|
+
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}` };
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
message: "No credentials file found at specified path",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Default location: remove from both keychain and default file
|
|
86
|
+
const removedFrom = [];
|
|
87
|
+
if (deleteKeychainCreds()) {
|
|
88
|
+
removedFrom.push("macOS Keychain");
|
|
89
|
+
}
|
|
90
|
+
if (deleteFileCreds()) {
|
|
91
|
+
removedFrom.push(getAuthFilePath());
|
|
92
|
+
}
|
|
93
|
+
if (removedFrom.length === 0) {
|
|
94
|
+
return { ok: true, message: "No credentials found" };
|
|
95
|
+
}
|
|
96
|
+
return { ok: true, message: `Removed from ${removedFrom.join(" and ")}` };
|
|
97
|
+
},
|
|
98
|
+
getAccessToken(creds) {
|
|
99
|
+
const data = creds.data;
|
|
100
|
+
if (creds.type === "api-key" && typeof data.apiKey === "string") {
|
|
101
|
+
return data.apiKey;
|
|
102
|
+
}
|
|
103
|
+
if (creds.type === "oauth") {
|
|
104
|
+
// Direct access_token
|
|
105
|
+
if (typeof data.access_token === "string") {
|
|
106
|
+
return data.access_token;
|
|
107
|
+
}
|
|
108
|
+
// Nested under tokens (from keychain/file storage)
|
|
109
|
+
const tokens = data.tokens;
|
|
110
|
+
if (typeof tokens?.access_token === "string") {
|
|
111
|
+
return tokens.access_token;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
},
|
|
116
|
+
credentialsToEnvironment(creds) {
|
|
117
|
+
const environment = {};
|
|
118
|
+
const data = creds.data;
|
|
119
|
+
if (creds.type === "api-key" && typeof data.apiKey === "string") {
|
|
120
|
+
environment.OPENAI_API_KEY = data.apiKey;
|
|
121
|
+
}
|
|
122
|
+
// OAuth tokens for Codex require auth.json file, not env vars
|
|
123
|
+
return environment;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
export { codexAdapter };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot CLI token discovery.
|
|
3
|
+
*
|
|
4
|
+
* Finds the first available token from environment, keychain, file, or gh CLI.
|
|
5
|
+
*/
|
|
6
|
+
/** Token source for auth method display */
|
|
7
|
+
type TokenSource = "environment" | "keychain" | "file" | "gh-cli";
|
|
8
|
+
/** Result of finding an available token */
|
|
9
|
+
interface TokenResult {
|
|
10
|
+
token: string;
|
|
11
|
+
source: TokenSource;
|
|
12
|
+
/** Environment variable name if source is "environment" */
|
|
13
|
+
environmentVariable?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Find the first available token from any source */
|
|
16
|
+
declare function findFirstAvailableToken(): TokenResult | undefined;
|
|
17
|
+
export { findFirstAvailableToken };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot CLI token discovery.
|
|
3
|
+
*
|
|
4
|
+
* Finds the first available token from environment, keychain, file, or gh CLI.
|
|
5
|
+
*/
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { getEnvironmentToken, getEnvironmentTokenSource, loadFileToken, loadKeychainToken, } from "./copilot-storage.js";
|
|
8
|
+
/** Try to get token from GitHub CLI */
|
|
9
|
+
function getGhCliToken() {
|
|
10
|
+
try {
|
|
11
|
+
const result = execFileSync("gh", ["auth", "token"], {
|
|
12
|
+
encoding: "utf8",
|
|
13
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
14
|
+
timeout: 5000,
|
|
15
|
+
});
|
|
16
|
+
const token = result.trim();
|
|
17
|
+
// Reject classic PATs (ghp_ prefix) - copilot requires fine-grained tokens
|
|
18
|
+
if (token && !token.startsWith("ghp_")) {
|
|
19
|
+
return token;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// gh CLI not available or not authenticated
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
/** Find the first available token from any source */
|
|
28
|
+
function findFirstAvailableToken() {
|
|
29
|
+
const environmentVariable = getEnvironmentTokenSource();
|
|
30
|
+
if (environmentVariable) {
|
|
31
|
+
const token = getEnvironmentToken();
|
|
32
|
+
if (token)
|
|
33
|
+
return { token, source: "environment", environmentVariable };
|
|
34
|
+
}
|
|
35
|
+
const keychainToken = loadKeychainToken();
|
|
36
|
+
if (keychainToken)
|
|
37
|
+
return { token: keychainToken, source: "keychain" };
|
|
38
|
+
const fileToken = loadFileToken();
|
|
39
|
+
if (fileToken)
|
|
40
|
+
return { token: fileToken, source: "file" };
|
|
41
|
+
const ghToken = getGhCliToken();
|
|
42
|
+
if (ghToken)
|
|
43
|
+
return { token: ghToken, source: "gh-cli" };
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
export { findFirstAvailableToken };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot CLI credential storage operations.
|
|
3
|
+
*
|
|
4
|
+
* Copilot CLI stores credentials in:
|
|
5
|
+
* - macOS Keychain: service "copilot-cli", account "<host>:<login>"
|
|
6
|
+
* - File: ~/.copilot/config.json with "copilot_tokens" field
|
|
7
|
+
*/
|
|
8
|
+
/** Get the copilot config directory */
|
|
9
|
+
declare function getConfigDirectory(): string;
|
|
10
|
+
/** Get the default config file path */
|
|
11
|
+
declare function getConfigFilePath(): string;
|
|
12
|
+
/** Load token from keychain */
|
|
13
|
+
declare function loadKeychainToken(host?: string): string | undefined;
|
|
14
|
+
/** Save token to keychain */
|
|
15
|
+
declare function saveKeychainToken(token: string, host?: string): boolean;
|
|
16
|
+
/** Delete token from keychain */
|
|
17
|
+
declare function deleteKeychainToken(host?: string): boolean;
|
|
18
|
+
/** Load token from config file */
|
|
19
|
+
declare function loadFileToken(host?: string): string | undefined;
|
|
20
|
+
/** Save token to config file */
|
|
21
|
+
declare function saveFileToken(token: string, host?: string): boolean;
|
|
22
|
+
/** Delete token from config file */
|
|
23
|
+
declare function deleteFileToken(host?: string): boolean;
|
|
24
|
+
/** Get token from environment variables (in priority order) */
|
|
25
|
+
declare function getEnvironmentToken(): string | undefined;
|
|
26
|
+
/** Get the name of the environment variable providing the token */
|
|
27
|
+
declare function getEnvironmentTokenSource(): "COPILOT_GITHUB_TOKEN" | "GH_TOKEN" | "GITHUB_TOKEN" | undefined;
|
|
28
|
+
/** Save token to a custom path (atomic write) */
|
|
29
|
+
declare function saveTokenToPath(token: string, targetPath: string): boolean;
|
|
30
|
+
/** Delete token from a custom path */
|
|
31
|
+
declare function deleteTokenFromPath(targetPath: string): boolean;
|
|
32
|
+
export { deleteFileToken, deleteKeychainToken, deleteTokenFromPath, getConfigDirectory, getConfigFilePath, getEnvironmentToken, getEnvironmentTokenSource, loadFileToken, loadKeychainToken, saveFileToken, saveKeychainToken, saveTokenToPath, };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot CLI credential storage operations.
|
|
3
|
+
*
|
|
4
|
+
* Copilot CLI stores credentials in:
|
|
5
|
+
* - macOS Keychain: service "copilot-cli", account "<host>:<login>"
|
|
6
|
+
* - File: ~/.copilot/config.json with "copilot_tokens" field
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { copilotConfigReader } from "axconfig";
|
|
11
|
+
import { ensureDirectory, loadJsonFile, saveJsonFile, } from "../file-storage.js";
|
|
12
|
+
import { deleteFromKeychain, isMacOS, loadFromKeychain, saveToKeychain, } from "../keychain.js";
|
|
13
|
+
import { getResolvedConfigDirectory } from "../resolve-config-directory.js";
|
|
14
|
+
const KEYCHAIN_SERVICE = "copilot-cli";
|
|
15
|
+
const DEFAULT_HOST = "https://github.com";
|
|
16
|
+
/** Get the copilot config directory */
|
|
17
|
+
function getConfigDirectory() {
|
|
18
|
+
return getResolvedConfigDirectory(copilotConfigReader);
|
|
19
|
+
}
|
|
20
|
+
/** Get the default config file path */
|
|
21
|
+
function getConfigFilePath() {
|
|
22
|
+
return path.join(getConfigDirectory(), "config.json");
|
|
23
|
+
}
|
|
24
|
+
/** Get the current username for keychain account */
|
|
25
|
+
function getUsername() {
|
|
26
|
+
return process.env.USER ?? process.env.USERNAME ?? "user";
|
|
27
|
+
}
|
|
28
|
+
/** Build keychain account key */
|
|
29
|
+
function getKeychainAccount(host = DEFAULT_HOST) {
|
|
30
|
+
return `${host}:${getUsername()}`;
|
|
31
|
+
}
|
|
32
|
+
/** Load token from keychain */
|
|
33
|
+
function loadKeychainToken(host = DEFAULT_HOST) {
|
|
34
|
+
if (!isMacOS())
|
|
35
|
+
return undefined;
|
|
36
|
+
const account = getKeychainAccount(host);
|
|
37
|
+
return loadFromKeychain(KEYCHAIN_SERVICE, account);
|
|
38
|
+
}
|
|
39
|
+
/** Save token to keychain */
|
|
40
|
+
function saveKeychainToken(token, host = DEFAULT_HOST) {
|
|
41
|
+
if (!isMacOS())
|
|
42
|
+
return false;
|
|
43
|
+
const account = getKeychainAccount(host);
|
|
44
|
+
return saveToKeychain(KEYCHAIN_SERVICE, account, token);
|
|
45
|
+
}
|
|
46
|
+
/** Delete token from keychain */
|
|
47
|
+
function deleteKeychainToken(host = DEFAULT_HOST) {
|
|
48
|
+
if (!isMacOS())
|
|
49
|
+
return false;
|
|
50
|
+
const account = getKeychainAccount(host);
|
|
51
|
+
return deleteFromKeychain(KEYCHAIN_SERVICE, account);
|
|
52
|
+
}
|
|
53
|
+
/** Load config file */
|
|
54
|
+
function loadConfig() {
|
|
55
|
+
return loadJsonFile(getConfigFilePath());
|
|
56
|
+
}
|
|
57
|
+
/** Save config file */
|
|
58
|
+
function saveConfig(config) {
|
|
59
|
+
return saveJsonFile(getConfigFilePath(), config, { mode: 0o600 });
|
|
60
|
+
}
|
|
61
|
+
/** Load token from config file */
|
|
62
|
+
function loadFileToken(host = DEFAULT_HOST) {
|
|
63
|
+
const config = loadConfig();
|
|
64
|
+
return config?.copilot_tokens?.[`${host}:${getUsername()}`];
|
|
65
|
+
}
|
|
66
|
+
/** Save token to config file */
|
|
67
|
+
function saveFileToken(token, host = DEFAULT_HOST) {
|
|
68
|
+
const config = loadConfig() ?? {};
|
|
69
|
+
const key = `${host}:${getUsername()}`;
|
|
70
|
+
config.copilot_tokens = {
|
|
71
|
+
...config.copilot_tokens,
|
|
72
|
+
[key]: token,
|
|
73
|
+
};
|
|
74
|
+
config.store_token_plaintext = true;
|
|
75
|
+
return saveConfig(config);
|
|
76
|
+
}
|
|
77
|
+
/** Delete token from config file */
|
|
78
|
+
function deleteFileToken(host = DEFAULT_HOST) {
|
|
79
|
+
const config = loadConfig();
|
|
80
|
+
if (!config?.copilot_tokens)
|
|
81
|
+
return false;
|
|
82
|
+
const key = `${host}:${getUsername()}`;
|
|
83
|
+
if (!(key in config.copilot_tokens))
|
|
84
|
+
return false;
|
|
85
|
+
// Remove the token by filtering out the key
|
|
86
|
+
const remainingTokens = Object.fromEntries(Object.entries(config.copilot_tokens).filter(([k]) => k !== key));
|
|
87
|
+
// If no tokens left, remove the whole section
|
|
88
|
+
if (Object.keys(remainingTokens).length === 0) {
|
|
89
|
+
const rest = Object.fromEntries(Object.entries(config).filter(([k]) => k !== "copilot_tokens" && k !== "store_token_plaintext"));
|
|
90
|
+
return saveConfig(rest);
|
|
91
|
+
}
|
|
92
|
+
return saveConfig({ ...config, copilot_tokens: remainingTokens });
|
|
93
|
+
}
|
|
94
|
+
/** Get token from environment variables (in priority order) */
|
|
95
|
+
function getEnvironmentToken() {
|
|
96
|
+
return (process.env.COPILOT_GITHUB_TOKEN ??
|
|
97
|
+
process.env.GH_TOKEN ??
|
|
98
|
+
process.env.GITHUB_TOKEN);
|
|
99
|
+
}
|
|
100
|
+
/** Get the name of the environment variable providing the token */
|
|
101
|
+
function getEnvironmentTokenSource() {
|
|
102
|
+
if (process.env.COPILOT_GITHUB_TOKEN)
|
|
103
|
+
return "COPILOT_GITHUB_TOKEN";
|
|
104
|
+
if (process.env.GH_TOKEN)
|
|
105
|
+
return "GH_TOKEN";
|
|
106
|
+
if (process.env.GITHUB_TOKEN)
|
|
107
|
+
return "GITHUB_TOKEN";
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
/** Save token to a custom path (atomic write) */
|
|
111
|
+
function saveTokenToPath(token, targetPath) {
|
|
112
|
+
const parentDirectory = path.dirname(targetPath);
|
|
113
|
+
if (!ensureDirectory(parentDirectory)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const temporaryFile = `${targetPath}.tmp.${Date.now()}`;
|
|
118
|
+
writeFileSync(temporaryFile, JSON.stringify({ accessToken: token }, undefined, 2), { mode: 0o600 });
|
|
119
|
+
renameSync(temporaryFile, targetPath);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Delete token from a custom path */
|
|
127
|
+
function deleteTokenFromPath(targetPath) {
|
|
128
|
+
if (!existsSync(targetPath)) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
unlinkSync(targetPath);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export { deleteFileToken, deleteKeychainToken, deleteTokenFromPath, getConfigDirectory, getConfigFilePath, getEnvironmentToken, getEnvironmentTokenSource, loadFileToken, loadKeychainToken, saveFileToken, saveKeychainToken, saveTokenToPath, };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot CLI auth adapter.
|
|
3
|
+
*
|
|
4
|
+
* Supports OAuth via keychain (macOS) or file, and GitHub token via env var.
|
|
5
|
+
* Also supports GitHub CLI (`gh auth login`) as a fallback.
|
|
6
|
+
*/
|
|
7
|
+
import type { AuthAdapter } from "../adapter.js";
|
|
8
|
+
/** Copilot CLI authentication adapter */
|
|
9
|
+
declare const copilotAdapter: AuthAdapter;
|
|
10
|
+
export { copilotAdapter };
|