Skip to content

Instantly share code, notes, and snippets.

@acreeger
Created June 27, 2025 19:30
Show Gist options
  • Save acreeger/ffd333b6e32f9e6e0dd4017c004381b4 to your computer and use it in GitHub Desktop.
Save acreeger/ffd333b6e32f9e6e0dd4017c004381b4 to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
const fs = require('fs');
const { execSync } = require('child_process');
const path = require('path');
// Parse command line arguments
function parseArgs() {
const args = process.argv.slice(2);
const options = {};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '-k':
case '--keychain-entry':
options.keychainEntry = args[++i];
break;
case '-s':
case '--secret-name':
options.secretName = args[++i];
break;
case '-r':
case '--repo':
options.repo = args[++i];
break;
case '-h':
case '--help':
console.log('Usage: node update_oauth_creds.js -k <keychain-entry> -s <secret-name> [-r <repo>]');
console.log(' -k, --keychain-entry Name of keychain entry');
console.log(' -s, --secret-name GitHub secret name');
console.log(' -r, --repo Repository (optional, uses current if not specified)');
process.exit(0);
default:
console.error(`Unknown option: ${args[i]}`);
process.exit(1);
}
}
return options;
}
// Check if required tools are available
function checkRequiredTools() {
try {
execSync('which security', { stdio: 'ignore' });
execSync('which gh', { stdio: 'ignore' });
} catch (error) {
if (error.status === 1) {
console.error('Error: Required tools (security or gh) not found');
process.exit(1);
}
}
}
// Parse JSON from comment at top of file
function parseJson(jsonData) {
try {
// Parse the JSON
const parsed = JSON.parse(jsonData);
const oauth = parsed.claudeAiOauth;
// Extract the values
const accessToken = oauth.accessToken;
const refreshToken = oauth.refreshToken;
const expiresAt = oauth.expiresAt;
return oauth
} catch (error) {
console.error('Error parsing JSON:', error.message);
}
return null;
}
// Get password from keychain
function getKeychainPassword(keychainEntry) {
try {
console.log('Retrieving password from keychain...');
const result = execSync(`security find-generic-password -s "${keychainEntry}" -w`, {
encoding: 'utf8'
});
return result.trim();
} catch (error) {
console.error('Error: Could not retrieve password from keychain');
process.exit(1);
}
}
// Update GitHub secret
function updateGitHubSecret(secretName, secretValue, repo) {
try {
console.log('Updating GitHub secret...');
const cmd = repo
? `gh secret set "${secretName}" --repo "${repo}"`
: `gh secret set "${secretName}"`;
execSync(cmd, {
input: secretValue,
stdio: ['pipe', 'inherit', 'inherit']
});
console.log(`Successfully updated GitHub secret: ${secretName}`);
} catch (error) {
console.error('Error updating GitHub secret:', error.message);
process.exit(1);
}
}
// Main function
function main() {
const options = parseArgs();
const keychainEntry = options.keychainEntry || 'Claude Code-credentials';
checkRequiredTools();
const secretValue = getKeychainPassword(keychainEntry);
if (!secretValue) {
console.error('Error: No secret value found in keychain');
process.exit(1);
}
const { accessToken, refreshToken, expiresAt } = parseJson(secretValue);
if (!accessToken || !refreshToken || !expiresAt) {
console.error('Error: Invalid OAuth credentials');
process.exit(1);
}
// console.log(`CLAUDE_ACCESS_TOKEN="${accessToken}"`);
// console.log(`CLAUDE_REFRESH_TOKEN="${refreshToken}"`);
console.log('Updating GitHub secrets...');
console.log("Updating Access Token");
updateGitHubSecret("CLAUDE_ACCESS_TOKEN", accessToken, options.repo);
console.log("Updating Refresh Token");
updateGitHubSecret("CLAUDE_REFRESH_TOKEN", refreshToken, options.repo);
console.log(`Updating Expires At: ${expiresAt} converted to UTC: ${new Date(expiresAt).toISOString()}`);
updateGitHubSecret("CLAUDE_EXPIRES_AT", expiresAt.toString(), options.repo);
console.log('All secrets updated successfully!');
}
// Run the script
if (require.main === module) {
main();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment