/**
* macOS Keychain integration for reading tokens stored by onboarded-cli.
*
* Uses the `security` CLI to read generic passwords from Keychain.
* This matches the Python CLI's keyring storage format:
* - Service: "onboarded-cli"
* - Account: "{profile}:token"
*/
import { execSync } from "node:child_process";
const KEYCHAIN_SERVICE = "onboarded-cli";
export interface KeychainResult {
found: boolean;
token?: string;
error?: string;
profile?: string;
}
export interface GetTokenOptions {
profile?: string;
}
// Alias for backward compatibility
export type TokenResult = KeychainResult;
/**
* Get a token from macOS Keychain for the given profile.
*
* @param options - Options including profile name
* @returns KeychainResult with found=true and token if successful
*/
export function getToken(options: GetTokenOptions | string = {}): KeychainResult {
const profile = typeof options === 'string' ? options : (options.profile ?? 'default');
const account = `${profile}:token`;
try {
// Use -w flag to output only the password (no other metadata)
const command = `security find-generic-password -s "${KEYCHAIN_SERVICE}" -a "${account}" -w 2>/dev/null`;
const token = execSync(command, {
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"],
}).trim();
if (token) {
return { found: true, token, profile };
}
return { found: false, error: "Token is empty", profile };
} catch (error) {
// security command returns non-zero if password not found
return {
found: false,
error: `No token found for profile "${profile}". Run: onboarded auth login ${profile}`,
profile,
};
}
}
/**
* Check if a token exists for the given profile without retrieving it.
*
* @param profile - The profile name
* @returns true if token exists, false otherwise
*/
export function hasToken(profile: string): boolean {
const account = `${profile}:token`;
try {
// Use -l flag to find by label, but we just check exit code
const command = `security find-generic-password -s "${KEYCHAIN_SERVICE}" -a "${account}" 2>/dev/null`;
execSync(command, {
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"],
});
return true;
} catch {
return false;
}
}
/**
* List all profiles that have tokens stored in Keychain.
*
* Note: This is a best-effort implementation. The macOS security CLI
* doesn't have a great way to list all items by service, so we'd need
* to know the profile names in advance or scan the config file.
*
* @param knownProfiles - Array of profile names to check
* @returns Array of profile names that have tokens
*/
export function listProfilesWithTokens(knownProfiles: string[]): string[] {
return knownProfiles.filter((profile) => hasToken(profile));
}