UNPKG

@awesomate/hosting-mcp

Version:

Awesomate MCP server — lets Claude manage your Awesomate WordPress hosting, plan, limits, n8n automations, and build Node/static apps + databases

101 lines (92 loc) • 4.02 kB
#!/usr/bin/env node /** * Shared account resolver for the awesomate-hosting skill scripts. * * Mirrors the MCP server's precedence exactly (mcp/src/config.ts): * AWESOMATE_ACCOUNT env → .awesomate.json pin (walked up from cwd, stopping * at $HOME) → sole profile → defaultProfile → legacy v1 top-level fields. * A pin naming a missing profile is a hard error — never a silent fallback. * * Prints shell-eval assignments (single-quoted, quote-stripped values — same * hardening contract the callers' charclass gates assume): * node resolve-account.mjs --ssh # ACCT + H/U/P/K * node resolve-account.mjs --api # ACCT + API/PAT * Flags combine. Exit 1 = no resolvable account, exit 2 = no shell access. */ import { existsSync, readFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { join, dirname, resolve } from 'node:path'; function readJson(path) { try { const parsed = JSON.parse(readFileSync(path, 'utf8')); return parsed && typeof parsed === 'object' ? parsed : null; } catch { return null; } } function fail(msg, code = 1) { console.error(msg); process.exit(code); } const credPath = join(homedir(), '.awesomate', 'credentials.json'); const file = readJson(credPath) ?? {}; const profiles = {}; if (file.profiles && typeof file.profiles === 'object') { for (const [key, profile] of Object.entries(file.profiles)) { if (profile && typeof profile.pat === 'string') profiles[key] = profile; } } else if (typeof file.pat === 'string' && file.pat) { profiles[file.slug || 'default'] = file; // legacy v1 single-account file } const names = Object.keys(profiles); function findPin() { const explicit = process.env.AWESOMATE_PIN_FILE; if (explicit) return existsSync(explicit) ? explicit : null; const home = resolve(homedir()); let dir = resolve(process.cwd()); for (;;) { const candidate = join(dir, '.awesomate.json'); if (existsSync(candidate)) return candidate; if (dir === home) return null; const parent = dirname(dir); if (parent === dir) return null; dir = parent; } } let key = null; if (process.env.AWESOMATE_ACCOUNT) { key = process.env.AWESOMATE_ACCOUNT; if (!profiles[key]) { fail(`AWESOMATE_ACCOUNT="${key}" has no matching profile in ${credPath}. Available: ${names.join(', ') || '(none)'}.`); } } else { const pinPath = findPin(); if (pinPath) { const pin = readJson(pinPath); key = pin && typeof pin.account === 'string' ? pin.account : null; if (!key) fail(`${pinPath} exists but has no "account" field. Expected: {"account": "<slug>"}.`); if (!profiles[key]) { fail( `This folder is pinned to account "${key}" (${pinPath}) but no matching profile exists in ${credPath}. ` + `Available: ${names.join(', ') || '(none)'}. Connect "${key}" from hub.awesomate.ai/claude (logged in as it) or fix the pin.`, ); } } else if (names.length === 1) { key = names[0]; } else if (file.defaultProfile && profiles[file.defaultProfile]) { key = file.defaultProfile; } else if (names.length === 0) { fail('Not connected. Run the Connect Claude Code setup from hub.awesomate.ai/claude.'); } else { fail( `Multiple accounts are connected (${names.join(', ')}) and this folder isn't pinned to one. ` + 'Create .awesomate.json {"account": "<slug>"} in the project root or set AWESOMATE_ACCOUNT.', ); } } const profile = profiles[key]; const q = (s) => "'" + String(s == null ? '' : s).replace(/'/g, '') + "'"; const out = [`ACCT=${q(profile.slug ?? key)}`]; if (process.argv.includes('--ssh')) { if (!profile.ssh) fail('Your plan does not include shell access (Support Plus+ required).', 2); out.push(`H=${q(profile.ssh.host)}`, `U=${q(profile.ssh.user)}`, `P=${q(profile.ssh.port || 22)}`, `K=${q(profile.ssh.keyPath)}`); } if (process.argv.includes('--api')) { out.push(`API=${q(profile.apiBase || 'https://hub.awesomate.ai')}`, `PAT=${q(profile.pat)}`); } console.log(out.join('; '));