feat(auth): sync credentials from Claude CLI
Extend external CLI credential sync to also read from Claude CLI's ~/.claude/.credentials.json file. This allows users who authenticate with Claude CLI to automatically have those credentials available in Moltbot without manual configuration. - Reads OAuth tokens from Claude CLI credential store - Auto-refreshes when token expires - Respects same TTL and near-expiry thresholds as Qwen CLI sync
This commit is contained in:
parent
01e0d3a320
commit
e88559a141
@ -1,5 +1,9 @@
|
|||||||
import { readQwenCliCredentialsCached } from "../cli-credentials.js";
|
|
||||||
import {
|
import {
|
||||||
|
readClaudeCliCredentialsCached,
|
||||||
|
readQwenCliCredentialsCached,
|
||||||
|
} from "../cli-credentials.js";
|
||||||
|
import {
|
||||||
|
CLAUDE_CLI_PROFILE_ID,
|
||||||
EXTERNAL_CLI_NEAR_EXPIRY_MS,
|
EXTERNAL_CLI_NEAR_EXPIRY_MS,
|
||||||
EXTERNAL_CLI_SYNC_TTL_MS,
|
EXTERNAL_CLI_SYNC_TTL_MS,
|
||||||
QWEN_CLI_PROFILE_ID,
|
QWEN_CLI_PROFILE_ID,
|
||||||
@ -22,18 +26,20 @@ function shallowEqualOAuthCredentials(a: OAuthCredential | undefined, b: OAuthCr
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isExternalProfileFresh(cred: AuthProfileCredential | undefined, now: number): boolean {
|
function isExternalProfileFresh(
|
||||||
|
cred: AuthProfileCredential | undefined,
|
||||||
|
now: number,
|
||||||
|
provider: string,
|
||||||
|
): boolean {
|
||||||
if (!cred) return false;
|
if (!cred) return false;
|
||||||
if (cred.type !== "oauth" && cred.type !== "token") return false;
|
if (cred.type !== "oauth" && cred.type !== "token") return false;
|
||||||
if (cred.provider !== "qwen-portal") {
|
if (cred.provider !== provider) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (typeof cred.expires !== "number") return true;
|
if (typeof cred.expires !== "number") return true;
|
||||||
return cred.expires > now + EXTERNAL_CLI_NEAR_EXPIRY_MS;
|
return cred.expires > now + EXTERNAL_CLI_NEAR_EXPIRY_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync OAuth credentials from external CLI tools (Qwen Code CLI) into the store.
|
* Sync OAuth credentials from external CLI tools (Claude CLI, Qwen Code CLI) into the store.
|
||||||
*
|
*
|
||||||
* Returns true if any credentials were updated.
|
* Returns true if any credentials were updated.
|
||||||
*/
|
*/
|
||||||
@ -41,12 +47,50 @@ export function syncExternalCliCredentials(store: AuthProfileStore): boolean {
|
|||||||
let mutated = false;
|
let mutated = false;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Sync from Claude CLI (~/.claude/.credentials.json)
|
||||||
|
const existingClaude = store.profiles[CLAUDE_CLI_PROFILE_ID];
|
||||||
|
const shouldSyncClaude =
|
||||||
|
!existingClaude ||
|
||||||
|
existingClaude.provider !== "anthropic" ||
|
||||||
|
!isExternalProfileFresh(existingClaude, now, "anthropic");
|
||||||
|
const claudeCreds = shouldSyncClaude
|
||||||
|
? readClaudeCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS })
|
||||||
|
: null;
|
||||||
|
if (claudeCreds) {
|
||||||
|
const existing = store.profiles[CLAUDE_CLI_PROFILE_ID];
|
||||||
|
const existingTyped =
|
||||||
|
existing?.type === "oauth" || existing?.type === "token" ? existing : undefined;
|
||||||
|
const shouldUpdate =
|
||||||
|
!existingTyped ||
|
||||||
|
existingTyped.provider !== "anthropic" ||
|
||||||
|
(typeof existingTyped.expires === "number" && existingTyped.expires <= now) ||
|
||||||
|
(typeof claudeCreds.expires === "number" &&
|
||||||
|
typeof existingTyped.expires === "number" &&
|
||||||
|
claudeCreds.expires > existingTyped.expires);
|
||||||
|
|
||||||
|
if (shouldUpdate) {
|
||||||
|
const isSame =
|
||||||
|
claudeCreds.type === "oauth" &&
|
||||||
|
existingTyped?.type === "oauth" &&
|
||||||
|
shallowEqualOAuthCredentials(existingTyped, claudeCreds);
|
||||||
|
if (!isSame) {
|
||||||
|
store.profiles[CLAUDE_CLI_PROFILE_ID] = claudeCreds;
|
||||||
|
mutated = true;
|
||||||
|
log.info("synced claude credentials from claude cli", {
|
||||||
|
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||||
|
type: claudeCreds.type,
|
||||||
|
expires: new Date(claudeCreds.expires).toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sync from Qwen Code CLI
|
// Sync from Qwen Code CLI
|
||||||
const existingQwen = store.profiles[QWEN_CLI_PROFILE_ID];
|
const existingQwen = store.profiles[QWEN_CLI_PROFILE_ID];
|
||||||
const shouldSyncQwen =
|
const shouldSyncQwen =
|
||||||
!existingQwen ||
|
!existingQwen ||
|
||||||
existingQwen.provider !== "qwen-portal" ||
|
existingQwen.provider !== "qwen-portal" ||
|
||||||
!isExternalProfileFresh(existingQwen, now);
|
!isExternalProfileFresh(existingQwen, now, "qwen-portal");
|
||||||
const qwenCreds = shouldSyncQwen
|
const qwenCreds = shouldSyncQwen
|
||||||
? readQwenCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS })
|
? readQwenCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS })
|
||||||
: null;
|
: null;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user