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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/bin/axauth +17 -0
  4. package/dist/auth/adapter.d.ts +121 -0
  5. package/dist/auth/adapter.js +7 -0
  6. package/dist/auth/agents/claude-code-storage.d.ts +18 -0
  7. package/dist/auth/agents/claude-code-storage.js +66 -0
  8. package/dist/auth/agents/claude-code.d.ts +9 -0
  9. package/dist/auth/agents/claude-code.js +162 -0
  10. package/dist/auth/agents/codex-auth-check.d.ts +9 -0
  11. package/dist/auth/agents/codex-auth-check.js +101 -0
  12. package/dist/auth/agents/codex-config.d.ts +12 -0
  13. package/dist/auth/agents/codex-config.js +40 -0
  14. package/dist/auth/agents/codex-storage.d.ts +43 -0
  15. package/dist/auth/agents/codex-storage.js +82 -0
  16. package/dist/auth/agents/codex.d.ts +9 -0
  17. package/dist/auth/agents/codex.js +126 -0
  18. package/dist/auth/agents/copilot-auth-check.d.ts +17 -0
  19. package/dist/auth/agents/copilot-auth-check.js +46 -0
  20. package/dist/auth/agents/copilot-storage.d.ts +32 -0
  21. package/dist/auth/agents/copilot-storage.js +139 -0
  22. package/dist/auth/agents/copilot.d.ts +10 -0
  23. package/dist/auth/agents/copilot.js +144 -0
  24. package/dist/auth/agents/gemini-auth-check.d.ts +16 -0
  25. package/dist/auth/agents/gemini-auth-check.js +73 -0
  26. package/dist/auth/agents/gemini-storage.d.ts +24 -0
  27. package/dist/auth/agents/gemini-storage.js +69 -0
  28. package/dist/auth/agents/gemini.d.ts +9 -0
  29. package/dist/auth/agents/gemini.js +157 -0
  30. package/dist/auth/agents/opencode.d.ts +9 -0
  31. package/dist/auth/agents/opencode.js +128 -0
  32. package/dist/auth/file-storage.d.ts +14 -0
  33. package/dist/auth/file-storage.js +63 -0
  34. package/dist/auth/keychain.d.ts +12 -0
  35. package/dist/auth/keychain.js +56 -0
  36. package/dist/auth/registry.d.ts +109 -0
  37. package/dist/auth/registry.js +145 -0
  38. package/dist/auth/resolve-config-directory.d.ts +14 -0
  39. package/dist/auth/resolve-config-directory.js +16 -0
  40. package/dist/auth/types.d.ts +19 -0
  41. package/dist/auth/types.js +4 -0
  42. package/dist/cli.d.ts +7 -0
  43. package/dist/cli.js +74 -0
  44. package/dist/commands/auth.d.ts +34 -0
  45. package/dist/commands/auth.js +155 -0
  46. package/dist/crypto.d.ts +39 -0
  47. package/dist/crypto.js +78 -0
  48. package/dist/index.d.ts +37 -0
  49. package/dist/index.js +39 -0
  50. package/package.json +96 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Auth commands - manage agent credentials.
3
+ */
4
+ import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
5
+ import promptPassword from "@inquirer/password";
6
+ import { checkAllAuth, extractRawCredentials, getAccessToken, installCredentials, removeCredentials, } from "../auth/registry.js";
7
+ import { DEFAULT_PASSWORD, encrypt, fromBase64, toBase64, tryDecrypt, } from "../crypto.js";
8
+ import { AGENT_CLIS } from "axshared";
9
+ /** Validate agent ID. Returns undefined if valid, sets exitCode and returns error message if invalid. */
10
+ function validateAgent(agent) {
11
+ if (!AGENT_CLIS.includes(agent)) {
12
+ console.error(`Error: Unknown agent '${agent}'`);
13
+ console.error(`Supported agents: ${AGENT_CLIS.join(", ")}`);
14
+ process.exitCode = 2;
15
+ return undefined;
16
+ }
17
+ return agent;
18
+ }
19
+ /** Handle auth list command */
20
+ function handleAuthList(options) {
21
+ const statuses = checkAllAuth();
22
+ if (options.json) {
23
+ console.log(JSON.stringify(statuses, undefined, 2));
24
+ }
25
+ else {
26
+ printAuthTable(statuses);
27
+ }
28
+ }
29
+ /** Handle auth token command */
30
+ function handleAuthToken(options) {
31
+ const agentId = validateAgent(options.agent);
32
+ if (!agentId)
33
+ return;
34
+ const creds = extractRawCredentials(agentId);
35
+ if (!creds) {
36
+ console.error(`Error: No credentials found for ${agentId}`);
37
+ process.exitCode = 1;
38
+ return;
39
+ }
40
+ // Extract the token based on credential type
41
+ const token = getAccessToken(creds);
42
+ if (!token) {
43
+ console.error(`Error: No access token available for ${agentId}`);
44
+ process.exitCode = 1;
45
+ return;
46
+ }
47
+ // Output just the token (no newline for piping)
48
+ process.stdout.write(token);
49
+ }
50
+ /** Handle auth export command */
51
+ async function handleAuthExport(options) {
52
+ const agentId = validateAgent(options.agent);
53
+ if (!agentId)
54
+ return;
55
+ const creds = extractRawCredentials(agentId);
56
+ if (!creds) {
57
+ console.error(`Error: No credentials found for ${agentId}`);
58
+ process.exitCode = 1;
59
+ return;
60
+ }
61
+ // Get password
62
+ const userPassword = options.password
63
+ ? await promptPassword({ message: "Encryption password" })
64
+ : DEFAULT_PASSWORD;
65
+ // Encrypt and write atomically (write to temp, then rename)
66
+ const encrypted = encrypt(JSON.stringify(creds), userPassword);
67
+ const file = { version: 1, agent: agentId, ...toBase64(encrypted) };
68
+ const temporaryFile = `${options.output}.tmp.${Date.now()}`;
69
+ writeFileSync(temporaryFile, JSON.stringify(file, undefined, 2), {
70
+ mode: 0o600,
71
+ });
72
+ renameSync(temporaryFile, options.output);
73
+ console.error(`Credentials exported to ${options.output}`);
74
+ }
75
+ /** Print auth status as TSV table */
76
+ function printAuthTable(statuses) {
77
+ // Header row (UPPERCASE for visibility)
78
+ console.log("AGENT\tSTATUS\tMETHOD");
79
+ // Data rows
80
+ for (const s of statuses) {
81
+ const status = s.authenticated ? "authenticated" : "not_configured";
82
+ console.log(`${s.agentId}\t${status}\t${s.method ?? ""}`);
83
+ }
84
+ }
85
+ /** Handle auth remove-credentials command */
86
+ function handleAuthRemove(options) {
87
+ const agentId = validateAgent(options.agent);
88
+ if (!agentId)
89
+ return;
90
+ const result = removeCredentials(agentId, { path: options.path });
91
+ if (result.ok) {
92
+ console.error(result.message);
93
+ }
94
+ else {
95
+ console.error(`Error: ${result.message}`);
96
+ process.exitCode = 1;
97
+ }
98
+ }
99
+ /** Handle auth install-credentials command */
100
+ async function handleAuthInstall(options) {
101
+ const agentId = validateAgent(options.agent);
102
+ if (!agentId)
103
+ return;
104
+ // Validate storage option
105
+ const validStorageTypes = ["keychain", "file"];
106
+ let storage;
107
+ if (options.storage !== undefined) {
108
+ if (!validStorageTypes.includes(options.storage)) {
109
+ console.error(`Error: Invalid storage type '${options.storage}'`);
110
+ console.error(`Valid options: ${validStorageTypes.join(", ")}`);
111
+ process.exitCode = 2;
112
+ return;
113
+ }
114
+ storage = options.storage;
115
+ }
116
+ // Check if file exists
117
+ if (!existsSync(options.input)) {
118
+ console.error(`Error: Credentials file not found: ${options.input}`);
119
+ process.exitCode = 1;
120
+ return;
121
+ }
122
+ try {
123
+ const fileContent = JSON.parse(readFileSync(options.input, "utf8"));
124
+ // Verify agent matches
125
+ if (fileContent.agent !== agentId) {
126
+ console.error(`Error: Credentials file is for '${fileContent.agent}', not '${agentId}'`);
127
+ process.exitCode = 1;
128
+ return;
129
+ }
130
+ // Decrypt credentials (tries default password first, then prompts if needed)
131
+ const decrypted = await tryDecrypt(fromBase64(fileContent), () => promptPassword({ message: "Decryption password" }));
132
+ const creds = JSON.parse(decrypted);
133
+ // Verify decrypted credentials match the requested agent (defense-in-depth)
134
+ if (creds.agent !== agentId) {
135
+ console.error(`Error: Decrypted credentials are for '${creds.agent}', not '${agentId}'`);
136
+ process.exitCode = 1;
137
+ return;
138
+ }
139
+ // Install credentials
140
+ const result = installCredentials(creds, { path: options.path, storage });
141
+ if (result.ok) {
142
+ console.error(result.message);
143
+ }
144
+ else {
145
+ console.error(`Error: ${result.message}`);
146
+ process.exitCode = 1;
147
+ }
148
+ }
149
+ catch (error) {
150
+ const message = error instanceof Error ? error.message : String(error);
151
+ console.error(`Error: Failed to install credentials: ${message}`);
152
+ process.exitCode = 1;
153
+ }
154
+ }
155
+ export { handleAuthExport, handleAuthInstall, handleAuthList, handleAuthRemove, handleAuthToken, };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Encryption utilities for credential export/import.
3
+ *
4
+ * Uses AES-256-GCM with PBKDF2 key derivation.
5
+ */
6
+ /**
7
+ * Derive default password from predictable seed.
8
+ * Provides obfuscation, not security - attacker would need to understand derivation logic.
9
+ */
10
+ declare const DEFAULT_PASSWORD: string;
11
+ /** Encrypted data structure */
12
+ interface EncryptedData {
13
+ ciphertext: Buffer;
14
+ salt: Buffer;
15
+ iv: Buffer;
16
+ tag: Buffer;
17
+ }
18
+ /** Serialized encrypted file format */
19
+ interface EncryptedFile {
20
+ version: 1;
21
+ agent: string;
22
+ ciphertext: string;
23
+ salt: string;
24
+ iv: string;
25
+ tag: string;
26
+ }
27
+ /** Encrypt data with password */
28
+ declare function encrypt(data: string, password: string): EncryptedData;
29
+ /** Convert encrypted data to base64 for JSON serialization */
30
+ declare function toBase64(encrypted: EncryptedData): Omit<EncryptedFile, "version" | "agent">;
31
+ /** Convert base64 strings back to buffers */
32
+ declare function fromBase64(file: Omit<EncryptedFile, "version" | "agent">): EncryptedData;
33
+ /**
34
+ * Try decrypting with default password first, then prompt for custom.
35
+ * Returns decrypted string on success.
36
+ */
37
+ declare function tryDecrypt(encrypted: EncryptedData, promptFunction: () => Promise<string>): Promise<string>;
38
+ export { DEFAULT_PASSWORD, encrypt, fromBase64, toBase64, tryDecrypt };
39
+ export type { EncryptedFile };
package/dist/crypto.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Encryption utilities for credential export/import.
3
+ *
4
+ * Uses AES-256-GCM with PBKDF2 key derivation.
5
+ */
6
+ import { createCipheriv, createDecipheriv, createHash, pbkdf2Sync, randomBytes, } from "node:crypto";
7
+ const ALGORITHM = "aes-256-gcm";
8
+ const ITERATIONS = 100_000;
9
+ const KEY_LENGTH = 32;
10
+ const SALT_LENGTH = 16;
11
+ const IV_LENGTH = 12;
12
+ const TAG_LENGTH = 16;
13
+ /**
14
+ * Derive default password from predictable seed.
15
+ * Provides obfuscation, not security - attacker would need to understand derivation logic.
16
+ */
17
+ const DEFAULT_PASSWORD = createHash("sha256")
18
+ .update(["axauth", "credentials", "default", "v1"].join("."))
19
+ .digest("base64")
20
+ .slice(0, 32);
21
+ /** Encrypt data with password */
22
+ function encrypt(data, password) {
23
+ const salt = randomBytes(SALT_LENGTH);
24
+ const key = pbkdf2Sync(password, salt, ITERATIONS, KEY_LENGTH, "sha256");
25
+ const iv = randomBytes(IV_LENGTH);
26
+ const cipher = createCipheriv(ALGORITHM, key, iv, {
27
+ authTagLength: TAG_LENGTH,
28
+ });
29
+ const ciphertext = Buffer.concat([
30
+ cipher.update(data, "utf8"),
31
+ cipher.final(),
32
+ ]);
33
+ return { ciphertext, salt, iv, tag: cipher.getAuthTag() };
34
+ }
35
+ /** Decrypt data with password. Throws on wrong password. */
36
+ function decrypt(encrypted, password) {
37
+ const key = pbkdf2Sync(password, encrypted.salt, ITERATIONS, KEY_LENGTH, "sha256");
38
+ const decipher = createDecipheriv(ALGORITHM, key, encrypted.iv, {
39
+ authTagLength: TAG_LENGTH,
40
+ });
41
+ decipher.setAuthTag(encrypted.tag);
42
+ return (decipher.update(encrypted.ciphertext).toString("utf8") +
43
+ decipher.final("utf8"));
44
+ }
45
+ /** Convert encrypted data to base64 for JSON serialization */
46
+ function toBase64(encrypted) {
47
+ return {
48
+ ciphertext: encrypted.ciphertext.toString("base64"),
49
+ salt: encrypted.salt.toString("base64"),
50
+ iv: encrypted.iv.toString("base64"),
51
+ tag: encrypted.tag.toString("base64"),
52
+ };
53
+ }
54
+ /** Convert base64 strings back to buffers */
55
+ function fromBase64(file) {
56
+ return {
57
+ ciphertext: Buffer.from(file.ciphertext, "base64"),
58
+ salt: Buffer.from(file.salt, "base64"),
59
+ iv: Buffer.from(file.iv, "base64"),
60
+ tag: Buffer.from(file.tag, "base64"),
61
+ };
62
+ }
63
+ /**
64
+ * Try decrypting with default password first, then prompt for custom.
65
+ * Returns decrypted string on success.
66
+ */
67
+ async function tryDecrypt(encrypted, promptFunction) {
68
+ // Try default password first
69
+ try {
70
+ return decrypt(encrypted, DEFAULT_PASSWORD);
71
+ }
72
+ catch {
73
+ // Default password failed, prompt user
74
+ const password = await promptFunction();
75
+ return decrypt(encrypted, password);
76
+ }
77
+ }
78
+ export { DEFAULT_PASSWORD, encrypt, fromBase64, toBase64, tryDecrypt };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * axauth - Authentication management library for AI coding agents.
3
+ *
4
+ * @example
5
+ * // Get access token for an agent
6
+ * import { getAgentAccessToken, checkAuth } from "axauth";
7
+ *
8
+ * const token = getAgentAccessToken("claude");
9
+ * if (token) {
10
+ * // Use token for API calls
11
+ * }
12
+ *
13
+ * // Or check auth status first
14
+ * const status = checkAuth("claude");
15
+ * if (status.authenticated) {
16
+ * console.log(`Authenticated via ${status.method}`);
17
+ * }
18
+ *
19
+ * @example
20
+ * // Use the adapter API
21
+ * import { getAdapter, getCapabilities } from "axauth";
22
+ *
23
+ * const adapter = getAdapter("claude");
24
+ * const status = adapter.checkAuth();
25
+ * const creds = adapter.extractCredentials();
26
+ *
27
+ * // Check what an agent supports
28
+ * const caps = getCapabilities("gemini");
29
+ * if (!caps.keychain) {
30
+ * console.log("Gemini doesn't support keychain storage");
31
+ * }
32
+ */
33
+ export type { AgentCli } from "axshared";
34
+ export { AGENT_CLIS } from "axshared";
35
+ export type { AdapterCapabilities, AuthAdapter, InstallOptions, OperationResult, RemoveOptions, } from "./auth/adapter.js";
36
+ export type { AuthStatus, Credentials } from "./auth/types.js";
37
+ export { checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, getAccessToken, getAgentAccessToken, installCredentials, removeCredentials, getAdapter, getAllAdapters, getCapabilities, } from "./auth/registry.js";
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * axauth - Authentication management library for AI coding agents.
3
+ *
4
+ * @example
5
+ * // Get access token for an agent
6
+ * import { getAgentAccessToken, checkAuth } from "axauth";
7
+ *
8
+ * const token = getAgentAccessToken("claude");
9
+ * if (token) {
10
+ * // Use token for API calls
11
+ * }
12
+ *
13
+ * // Or check auth status first
14
+ * const status = checkAuth("claude");
15
+ * if (status.authenticated) {
16
+ * console.log(`Authenticated via ${status.method}`);
17
+ * }
18
+ *
19
+ * @example
20
+ * // Use the adapter API
21
+ * import { getAdapter, getCapabilities } from "axauth";
22
+ *
23
+ * const adapter = getAdapter("claude");
24
+ * const status = adapter.checkAuth();
25
+ * const creds = adapter.extractCredentials();
26
+ *
27
+ * // Check what an agent supports
28
+ * const caps = getCapabilities("gemini");
29
+ * if (!caps.keychain) {
30
+ * console.log("Gemini doesn't support keychain storage");
31
+ * }
32
+ */
33
+ export { AGENT_CLIS } from "axshared";
34
+ // Registry - unified entry point for all operations
35
+ export {
36
+ // Core operations
37
+ checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, getAccessToken, getAgentAccessToken, installCredentials, removeCredentials,
38
+ // Adapter access
39
+ getAdapter, getAllAdapters, getCapabilities, } from "./auth/registry.js";
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "axauth",
3
+ "author": "Łukasz Jerciński",
4
+ "license": "MIT",
5
+ "version": "1.0.0",
6
+ "description": "Authentication management library and CLI for AI coding agents",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Jercik/axauth.git"
10
+ },
11
+ "homepage": "https://github.com/Jercik/axauth#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/Jercik/axauth/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "bin": {
25
+ "axauth": "bin/axauth"
26
+ },
27
+ "files": [
28
+ "bin/",
29
+ "dist/",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "scripts": {
34
+ "prepare": "git config core.hooksPath .githooks",
35
+ "prepublishOnly": "pnpm run rebuild",
36
+ "build": "tsc -p tsconfig.app.json",
37
+ "clean": "rm -rf dist *.tsbuildinfo",
38
+ "format": "prettier --write .",
39
+ "format:check": "prettier --check .",
40
+ "fta": "fta-check",
41
+ "knip": "knip",
42
+ "lint": "eslint",
43
+ "rebuild": "pnpm run clean && pnpm run build",
44
+ "start": "pnpm -s run rebuild && node bin/axauth",
45
+ "test": "vitest run",
46
+ "test:coverage": "vitest run --coverage",
47
+ "test:watch": "vitest",
48
+ "typecheck": "tsc -b --noEmit"
49
+ },
50
+ "keywords": [
51
+ "ai",
52
+ "agent",
53
+ "cli",
54
+ "auth",
55
+ "authentication",
56
+ "credentials",
57
+ "claude-code",
58
+ "codex",
59
+ "gemini",
60
+ "opencode",
61
+ "llm",
62
+ "automation",
63
+ "coding-assistant"
64
+ ],
65
+ "packageManager": "pnpm@10.26.1",
66
+ "engines": {
67
+ "node": ">=22.14.0"
68
+ },
69
+ "dependencies": {
70
+ "@commander-js/extra-typings": "^14.0.0",
71
+ "@inquirer/password": "^5.0.3",
72
+ "axconfig": "^2.0.0",
73
+ "axshared": "^1.1.0",
74
+ "commander": "^14.0.2"
75
+ },
76
+ "devDependencies": {
77
+ "@eslint/compat": "^2.0.0",
78
+ "@eslint/js": "^9.39.2",
79
+ "@total-typescript/ts-reset": "^0.6.1",
80
+ "@types/node": "^25.0.3",
81
+ "@vitest/coverage-v8": "^4.0.16",
82
+ "@vitest/eslint-plugin": "^1.6.4",
83
+ "eslint": "^9.39.2",
84
+ "eslint-config-prettier": "^10.1.8",
85
+ "eslint-plugin-unicorn": "^62.0.0",
86
+ "fta-check": "^1.5.1",
87
+ "fta-cli": "^3.0.0",
88
+ "globals": "^16.5.0",
89
+ "knip": "^5.78.0",
90
+ "prettier": "3.7.4",
91
+ "semantic-release": "^25.0.2",
92
+ "typescript": "^5.9.3",
93
+ "typescript-eslint": "^8.51.0",
94
+ "vitest": "^4.0.16"
95
+ }
96
+ }