#!/usr/bin/env node /** * Kakao OAuth Refresh Token Collector * * 1. Starts local server on :8080 * 2. Opens browser for authorization * 3. Exchanges code for tokens * 4. Saves refresh_token to openclaw.json */ const http = require('http'); const { spawn } = require('child_process'); const fs = require('fs'); const path = require('path'); const REST_API_KEY = process.env.KAKAO_REST_API_KEY || '4d7f36bbfa672c5e24582307de57f4e4'; const CLIENT_SECRET = process.env.KAKAO_CLIENT_SECRET; const REDIRECT_URI = 'http://localhost:8080/callback'; const SCOPE = 'talk_calendar'; if (!CLIENT_SECRET) { console.error('āŒ KAKAO_CLIENT_SECRET environment variable not set'); process.exit(1); } const CONFIG_PATH = path.join(process.env.HOME, '.openclaw', 'openclaw.json'); // Authorization URL const authUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&response_type=code&scope=${SCOPE}`; console.log('šŸ“± Kakao OAuth Refresh Token Collector\n'); console.log('šŸ”— Authorization URL:'); console.log(authUrl); console.log('\n🌐 Opening browser...\n'); // Open browser spawn('open', [authUrl]); // Start callback server const server = http.createServer(async (req, res) => { const url = new URL(req.url, `http://localhost:8080`); if (url.pathname === '/callback') { const code = url.searchParams.get('code'); const error = url.searchParams.get('error'); if (error) { res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(`

āŒ Authorization Failed

Error: ${error}

`); console.error('āŒ Authorization failed:', error); server.close(); process.exit(1); } if (!code) { res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' }); res.end('

āŒ No code received

'); console.error('āŒ No authorization code received'); server.close(); process.exit(1); } console.log('āœ… Authorization code received:', code); console.log('šŸ”„ Exchanging code for tokens...\n'); // Exchange code for tokens try { const tokenResponse = await fetch('https://kauth.kakao.com/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' }, body: new URLSearchParams({ grant_type: 'authorization_code', client_id: REST_API_KEY, client_secret: CLIENT_SECRET, redirect_uri: REDIRECT_URI, code: code }) }); const tokens = await tokenResponse.json(); if (tokens.error) { throw new Error(`Token exchange failed: ${tokens.error_description || tokens.error}`); } console.log('āœ… Tokens received:'); console.log(` Access Token: ${tokens.access_token.substring(0, 20)}...`); console.log(` Refresh Token: ${tokens.refresh_token.substring(0, 20)}...`); console.log(` Expires in: ${tokens.expires_in}s (${tokens.expires_in / 3600}h)`); console.log(` Refresh Token Expires in: ${tokens.refresh_token_expires_in}s (${tokens.refresh_token_expires_in / 86400}d)\n`); // Save to openclaw.json const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); if (!config.env) config.env = {}; if (!config.env.vars) config.env.vars = {}; config.env.vars.KAKAO_ACCESS_TOKEN = tokens.access_token; config.env.vars.KAKAO_REFRESH_TOKEN = tokens.refresh_token; config.env.vars.KAKAO_TOKEN_EXPIRES_AT = new Date(Date.now() + tokens.expires_in * 1000).toISOString(); config.env.vars.KAKAO_REFRESH_TOKEN_EXPIRES_AT = new Date(Date.now() + tokens.refresh_token_expires_in * 1000).toISOString(); fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2)); console.log('šŸ’¾ Tokens saved to:', CONFIG_PATH); console.log('āœ… Done! Gateway restart required.\n'); res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(`

āœ… Success!

Refresh token saved. You can close this window.

Next: Restart OpenClaw gateway.

`); server.close(); process.exit(0); } catch (error) { console.error('āŒ Token exchange failed:', error.message); res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(`

āŒ Token Exchange Failed

${error.message}

`); server.close(); process.exit(1); } } else { res.writeHead(404); res.end('Not Found'); } }); server.listen(8080, () => { console.log('šŸš€ Callback server listening on http://localhost:8080'); console.log('ā³ Waiting for authorization...\n'); }); // Timeout after 5 minutes setTimeout(() => { console.log('\nā° Timeout after 5 minutes'); server.close(); process.exit(1); }, 5 * 60 * 1000);