Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge remote-tracking branch 'tangled/main'

+182 -35
+96 -19
fedac/native/ac-os
··· 728 728 log "Authenticated: @${USER_HANDLE}" 729 729 } 730 730 731 + read_ac_access_token() { 732 + local TOKEN_FILE="${HOME}/.ac-token" 733 + [ -f "${TOKEN_FILE}" ] || return 1 734 + node -e " 735 + const t = JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8')); 736 + process.stdout.write(t.access_token || ''); 737 + " "${TOKEN_FILE}" 2>/dev/null 738 + } 739 + 740 + fetch_authenticated_handle_tokens() { 741 + local AC_ACCESS_TOKEN="${1:-}" 742 + [ -n "${AC_ACCESS_TOKEN}" ] || return 1 743 + curl -fsSL \ 744 + -H "Authorization: Bearer ${AC_ACCESS_TOKEN}" \ 745 + "https://aesthetic.computer/.netlify/functions/claude-token" \ 746 + --max-time 20 2>/dev/null 747 + } 748 + 749 + should_bake_local_claude_session() { 750 + local MODE="${AC_FLASH_INCLUDE_LOCAL_CLAUDE:-auto}" 751 + case "${MODE}" in 752 + 1|true|TRUE|yes|YES) 753 + return 0 754 + ;; 755 + 0|false|FALSE|no|NO) 756 + return 1 757 + ;; 758 + esac 759 + 760 + [ -n "${AC_USER_EMAIL:-}" ] || return 1 761 + [ -f "${HOME}/.claude.json" ] || return 1 762 + 763 + local LOCAL_EMAIL 764 + LOCAL_EMAIL=$(node -e " 765 + const d = JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8')); 766 + const email = d.oauthAccount?.emailAddress || ''; 767 + process.stdout.write(String(email).trim().toLowerCase()); 768 + " "${HOME}/.claude.json" 2>/dev/null) 769 + 770 + [ -n "${LOCAL_EMAIL}" ] || return 1 771 + [ "${LOCAL_EMAIL}" = "$(printf '%s' "${AC_USER_EMAIL}" | tr '[:upper:]' '[:lower:]')" ] 772 + } 773 + 731 774 flash_usb() { 732 775 local USB_DEV 733 776 local USB_CONFIG 734 777 local AC_ACCESS_TOKEN 778 + local HANDLE_TOKEN_JSON="" 779 + local HANDLE_TOKEN_HANDLE="" 780 + local CLAUDE_TOKEN="" 781 + local GITHUB_PAT="" 735 782 local CLAUDE_CREDS="" 736 783 local CLAUDE_STATE="" 737 784 local MEDIA_TMP ··· 754 801 755 802 # Build config.json from authenticated user. 756 803 if [ -n "${AC_USER_HANDLE:-}" ]; then 757 - AC_ACCESS_TOKEN=$(node -e "const t=JSON.parse(require('fs').readFileSync('${HOME}/.ac-token','utf8')); console.log(t.access_token || '')" 2>/dev/null) 758 - if [ -f "${HOME}/.claude/.credentials.json" ]; then 759 - CLAUDE_CREDS=$(cat "${HOME}/.claude/.credentials.json" 2>/dev/null) 804 + AC_ACCESS_TOKEN=$(read_ac_access_token) 805 + HANDLE_TOKEN_JSON=$(fetch_authenticated_handle_tokens "${AC_ACCESS_TOKEN}" || true) 806 + if [ -n "${HANDLE_TOKEN_JSON}" ]; then 807 + HANDLE_TOKEN_HANDLE=$(node -e " 808 + const d = JSON.parse(process.argv[1] || '{}'); 809 + process.stdout.write(d.handle || ''); 810 + " "${HANDLE_TOKEN_JSON}" 2>/dev/null) 811 + CLAUDE_TOKEN=$(node -e " 812 + const d = JSON.parse(process.argv[1] || '{}'); 813 + process.stdout.write(d.token || ''); 814 + " "${HANDLE_TOKEN_JSON}" 2>/dev/null) 815 + GITHUB_PAT=$(node -e " 816 + const d = JSON.parse(process.argv[1] || '{}'); 817 + process.stdout.write(d.githubPat || ''); 818 + " "${HANDLE_TOKEN_JSON}" 2>/dev/null) 819 + if [ -n "${HANDLE_TOKEN_HANDLE}" ] && [ "${HANDLE_TOKEN_HANDLE}" != "${AC_USER_HANDLE}" ]; then 820 + err "Authenticated handle mismatch: token API returned @${HANDLE_TOKEN_HANDLE}, CLI login is @${AC_USER_HANDLE}" 821 + fi 822 + log "Handle tokens: claude=$([ -n "${CLAUDE_TOKEN}" ] && echo yes || echo no) github=$([ -n "${GITHUB_PAT}" ] && echo yes || echo no)" 823 + else 824 + log "Handle tokens: unavailable" 760 825 fi 761 - if [ -f "${HOME}/.claude.json" ]; then 762 - CLAUDE_STATE=$(node -e " 763 - const d=JSON.parse(require('fs').readFileSync(process.env.HOME+'/.claude.json','utf8')); 764 - console.log(JSON.stringify({ 765 - oauthAccount:d.oauthAccount, 766 - hasCompletedOnboarding:true, 767 - installMethod:'manual', 768 - numStartups:1, 769 - autoUpdates:false, 770 - autoUpdatesProtectedForNative:true 771 - })); 772 - " 2>/dev/null) 826 + 827 + if should_bake_local_claude_session; then 828 + if [ -f "${HOME}/.claude/.credentials.json" ]; then 829 + CLAUDE_CREDS=$(cat "${HOME}/.claude/.credentials.json" 2>/dev/null) 830 + fi 831 + if [ -f "${HOME}/.claude.json" ]; then 832 + CLAUDE_STATE=$(node -e " 833 + const d=JSON.parse(require('fs').readFileSync(process.env.HOME+'/.claude.json','utf8')); 834 + console.log(JSON.stringify({ 835 + oauthAccount:d.oauthAccount, 836 + hasCompletedOnboarding:true, 837 + installMethod:'manual', 838 + numStartups:1, 839 + autoUpdates:false, 840 + autoUpdatesProtectedForNative:true 841 + })); 842 + " 2>/dev/null) 843 + fi 844 + log "Local Claude session: baked for ${AC_USER_EMAIL}" 845 + elif [ -f "${HOME}/.claude.json" ] || [ -f "${HOME}/.claude/.credentials.json" ]; then 846 + log "Local Claude session: skipped (email mismatch or disabled)" 773 847 fi 774 848 USB_CONFIG=$(node -e " 775 - const cfg = {handle:'${AC_USER_HANDLE}',sub:'${AC_USER_SUB}',email:'${AC_USER_EMAIL}',token:'${AC_ACCESS_TOKEN}'}; 776 - try { cfg.claudeCreds = JSON.parse(process.argv[1]); } catch(e) {} 777 - try { cfg.claudeState = JSON.parse(process.argv[2]); } catch(e) {} 849 + const [handle, sub, email, token, claudeToken, githubPat, claudeCreds, claudeState] = process.argv.slice(1); 850 + const cfg = { handle, sub, email, token }; 851 + if (claudeToken) cfg.claudeToken = claudeToken; 852 + if (githubPat) cfg.githubPat = githubPat; 853 + try { if (claudeCreds) cfg.claudeCreds = JSON.parse(claudeCreds); } catch (e) {} 854 + try { if (claudeState) cfg.claudeState = JSON.parse(claudeState); } catch (e) {} 778 855 console.log(JSON.stringify(cfg)); 779 - " "${CLAUDE_CREDS}" "${CLAUDE_STATE}" 2>/dev/null) 856 + " "${AC_USER_HANDLE}" "${AC_USER_SUB}" "${AC_USER_EMAIL}" "${AC_ACCESS_TOKEN}" "${CLAUDE_TOKEN}" "${GITHUB_PAT}" "${CLAUDE_CREDS}" "${CLAUDE_STATE}" 2>/dev/null) 780 857 else 781 858 USB_CONFIG='{"handle":"unknown"}' 782 859 fi
+3 -1
fedac/native/scripts/media-layout.sh
··· 209 209 if command -v jq >/dev/null 2>&1; then 210 210 jq -r ' 211 211 def yesno($v): if $v then "yes" else "no" end; 212 - "handle=@\(.handle // "unknown") ac_token=\(yesno((((.token // "") | tostring) | length) > 0)) claude_creds=\(yesno(.claudeCreds? != null)) claude_state=\(yesno(.claudeState? != null))" 212 + "handle=@\(.handle // "unknown") ac_token=\(yesno((((.token // "") | tostring) | length) > 0)) claude_token=\(yesno((((.claudeToken // "") | tostring) | length) > 0)) github_pat=\(yesno((((.githubPat // "") | tostring) | length) > 0)) claude_creds=\(yesno(.claudeCreds? != null)) claude_state=\(yesno(.claudeState? != null))" 213 213 ' "${config_path}" 2>/dev/null && return 0 214 214 fi 215 215 ··· 220 220 const parts = [ 221 221 'handle=@' + (cfg.handle || 'unknown'), 222 222 'ac_token=' + ((cfg.token || '').length > 0 ? 'yes' : 'no'), 223 + 'claude_token=' + ((cfg.claudeToken || '').length > 0 ? 'yes' : 'no'), 224 + 'github_pat=' + ((cfg.githubPat || '').length > 0 ? 'yes' : 'no'), 223 225 'claude_creds=' + (cfg.claudeCreds ? 'yes' : 'no'), 224 226 'claude_state=' + (cfg.claudeState ? 'yes' : 'no'), 225 227 ];
+51 -15
tezos/ac-login.mjs
··· 28 28 const CALLBACK_PORT = 44233; // Matches Auth0 allowed callback URL 29 29 const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}/callback`; 30 30 const TOKEN_FILE = join(process.env.HOME, '.ac-token'); 31 + const LOGOUT_RETURN_TO = process.env.AC_LOGOUT_RETURN_TO || 'https://aesthetic.computer'; 31 32 32 33 // PKCE helpers 33 34 function base64URLEncode(buffer) { ··· 62 63 } 63 64 } 64 65 66 + function buildAuthUrl(state, codeChallenge, forcePrompt = false) { 67 + const authUrl = new URL(`https://${AUTH0_DOMAIN}/authorize`); 68 + authUrl.searchParams.set('response_type', 'code'); 69 + authUrl.searchParams.set('client_id', AUTH0_CLIENT_ID); 70 + authUrl.searchParams.set('redirect_uri', REDIRECT_URI); 71 + authUrl.searchParams.set('scope', 'openid profile email offline_access'); 72 + authUrl.searchParams.set('state', state); 73 + authUrl.searchParams.set('code_challenge', codeChallenge); 74 + authUrl.searchParams.set('code_challenge_method', 'S256'); 75 + if (forcePrompt) authUrl.searchParams.set('prompt', 'login'); 76 + return authUrl; 77 + } 78 + 79 + function buildLogoutUrl() { 80 + const logoutUrl = new URL(`https://${AUTH0_DOMAIN}/v2/logout`); 81 + logoutUrl.searchParams.set('client_id', AUTH0_CLIENT_ID); 82 + logoutUrl.searchParams.set('returnTo', LOGOUT_RETURN_TO); 83 + return logoutUrl; 84 + } 85 + 65 86 // Main login flow 66 - async function login() { 87 + async function login({ forcePrompt = false } = {}) { 67 88 console.log('╔══════════════════════════════════════════════════════════════╗'); 68 89 console.log('║ 🔐 Aesthetic Computer CLI Login ║'); 69 90 console.log('╚══════════════════════════════════════════════════════════════╝\n'); ··· 73 94 const state = crypto.randomBytes(16).toString('hex'); 74 95 75 96 try { 76 - const { tokens, user } = await startLocalCallbackServer(state, codeVerifier, codeChallenge); 97 + const { tokens, user } = await startLocalCallbackServer(state, codeVerifier, codeChallenge, { forcePrompt }); 77 98 78 99 const displayName = user.handle ? `@${user.handle}` : user.email || user.name; 79 100 console.log('\n✅ Authentication successful!\n'); ··· 88 109 } 89 110 } 90 111 112 + async function logout({ browser = true } = {}) { 113 + let removed = false; 114 + try { 115 + await fs.unlink(TOKEN_FILE); 116 + removed = true; 117 + } catch {} 118 + 119 + if (removed) console.log('✅ Logged out locally\n'); 120 + else console.log('ℹ️ Already logged out locally\n'); 121 + 122 + if (!browser) return; 123 + 124 + const logoutUrl = buildLogoutUrl(); 125 + console.log('🌐 Opening browser to clear Auth0 session...\n'); 126 + console.log(' If your browser doesn\'t open, visit:'); 127 + console.log(` ${logoutUrl.toString()}\n`); 128 + openBrowser(logoutUrl.toString()); 129 + } 130 + 91 131 // Start local callback server 92 - async function startLocalCallbackServer(state, codeVerifier, codeChallenge) { 132 + async function startLocalCallbackServer(state, codeVerifier, codeChallenge, { forcePrompt = false } = {}) { 93 133 return new Promise((resolve, reject) => { 94 134 const server = http.createServer(async (req, res) => { 95 135 const url = new URL(req.url, `http://localhost:${CALLBACK_PORT}`); ··· 250 290 }); 251 291 252 292 server.listen(CALLBACK_PORT, () => { 253 - const authUrl = new URL(`https://${AUTH0_DOMAIN}/authorize`); 254 - authUrl.searchParams.set('response_type', 'code'); 255 - authUrl.searchParams.set('client_id', AUTH0_CLIENT_ID); 256 - authUrl.searchParams.set('redirect_uri', REDIRECT_URI); 257 - authUrl.searchParams.set('scope', 'openid profile email offline_access'); 258 - authUrl.searchParams.set('state', state); 259 - authUrl.searchParams.set('code_challenge', codeChallenge); 260 - authUrl.searchParams.set('code_challenge_method', 'S256'); 293 + const authUrl = buildAuthUrl(state, codeChallenge, forcePrompt); 261 294 262 295 console.log('🌐 Opening browser for authentication...\n'); 263 296 console.log(' If your browser doesn\'t open, visit:'); ··· 324 357 325 358 (async () => { 326 359 const command = process.argv[2]; 360 + const localOnly = process.argv.includes('--local-only'); 361 + const forcePrompt = command === 'fresh' || command === 'switch' || process.argv.includes('--fresh'); 327 362 328 363 if (command === 'logout') { 329 - try { await fs.unlink(TOKEN_FILE); console.log('✅ Logged out\n'); } 330 - catch { console.log('ℹ️ Already logged out\n'); } 364 + await logout({ browser: !localOnly }); 331 365 return; 332 366 } 333 367 ··· 351 385 352 386 Usage: 353 387 ac-login Login (opens browser) 388 + ac-login fresh Login with forced account prompt 354 389 ac-login status Check login status 355 - ac-login logout Clear stored token 390 + ac-login logout Clear local token + browser Auth0 session 391 + ac-login logout --local-only Clear local token only 356 392 ac-login token Print access token (for scripts) 357 393 ac-login help Show this help 358 394 `); 359 395 return; 360 396 } 361 397 362 - await login(); 398 + await login({ forcePrompt }); 363 399 })();
+32
tezos/install-ac-auth.sh
··· 1 + #!/usr/bin/env bash 2 + set -euo pipefail 3 + 4 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 5 + TARGET_DIR="${HOME}/.local/bin" 6 + 7 + mkdir -p "${TARGET_DIR}" 8 + 9 + write_wrapper() { 10 + local name="$1" 11 + local subcommand="$2" 12 + local target="${TARGET_DIR}/${name}" 13 + 14 + cat > "${target}" <<EOF 15 + #!/usr/bin/env bash 16 + exec node "${SCRIPT_DIR}/ac-login.mjs" ${subcommand} "\$@" 17 + EOF 18 + chmod +x "${target}" 19 + } 20 + 21 + write_wrapper "ac-login" "" 22 + write_wrapper "ac-login-fresh" "fresh" 23 + write_wrapper "ac-logout" "logout" 24 + write_wrapper "ac-status" "status" 25 + write_wrapper "ac-token" "token" 26 + 27 + echo "Installed AC auth commands to ${TARGET_DIR}:" 28 + echo " ac-login" 29 + echo " ac-login-fresh" 30 + echo " ac-logout" 31 + echo " ac-status" 32 + echo " ac-token"