pame-core-cli
Version:
PAME.AI Core Operating System CLI - Open Source AI Platform for Agentic Commerce
308 lines ⢠13.4 kB
JavaScript
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import inquirer from 'inquirer';
import { GoDaddyManager } from '../services/godaddy-manager.js';
import { VercelManager } from '../services/vercel-manager.js';
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
export function createDNSCommand() {
const dns = new Command('dns')
.description('Manage DNS records for PAME.AI subdomains')
.addHelpText('after', `
Examples:
$ pame-core dns add api --vercel-url pame-api-xxxxx.vercel.app
$ pame-core dns add crm --auto-create
$ pame-core dns list
$ pame-core dns verify api
`);
dns
.command('add <subdomain>')
.description('Add DNS records for a subdomain')
.option('--vercel-url <url>', 'Vercel deployment URL')
.option('--auto-create', 'Automatically create and deploy project')
.option('--template <template>', 'Project template to use', 'nextjs')
.option('--skip-deploy', 'Skip deployment step')
.action(async (subdomain, options) => {
const spinner = ora();
try {
console.log(chalk.cyan(`š Setting up ${subdomain}.pame.ai...\n`));
const godaddy = new GoDaddyManager();
const vercel = new VercelManager();
let vercelUrl = options.vercelUrl;
// Step 1: Auto-create project if requested
if (options.autoCreate && !options.skipDeploy) {
spinner.start('Creating Vercel project...');
const projectName = `pame-${subdomain}`;
const projectPath = path.join(process.cwd(), projectName);
// Create project directory
if (!fs.existsSync(projectPath)) {
fs.mkdirSync(projectPath, { recursive: true });
}
// Initialize project
process.chdir(projectPath);
spinner.text = 'Initializing Next.js project...';
execSync('npx create-next-app@latest . --typescript --tailwind --app --yes', {
stdio: 'ignore'
});
// Create basic page
const pageContent = `export default function ${capitalize(subdomain)}Page() {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
<div className="text-center">
<h1 className="text-4xl font-bold text-gray-800 mb-4">
${subdomain}.pame.ai
</h1>
<p className="text-xl text-gray-600">
Coming soon...
</p>
</div>
</div>
);
}`;
fs.writeFileSync('app/page.tsx', pageContent);
spinner.text = 'Linking to Vercel...';
// Link to Vercel
try {
execSync(`vercel link --yes --project ${projectName}`, {
stdio: 'ignore'
});
}
catch {
// Project might not exist, create it
execSync('vercel link --yes', {
stdio: 'pipe',
input: `y\n${projectName}\n`
});
}
spinner.text = 'Deploying to Vercel...';
// Deploy to production
const deployOutput = execSync('vercel --prod --yes', {
encoding: 'utf8'
});
// Extract deployment URL
const urlMatch = deployOutput.match(/https:\/\/[a-z0-9-]+\.vercel\.app/);
if (urlMatch) {
vercelUrl = urlMatch[0];
spinner.succeed(`Deployed to ${vercelUrl}`);
}
else {
spinner.fail('Could not extract deployment URL');
}
// Return to original directory
process.chdir('..');
}
// Step 2: Add custom domain in Vercel
if (!vercelUrl) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'vercelUrl',
message: 'Enter your Vercel deployment URL:',
validate: (input) => {
return input.includes('.vercel.app') || 'Please enter a valid Vercel URL';
}
}
]);
vercelUrl = answers.vercelUrl;
}
spinner.start(`Adding ${subdomain}.pame.ai to Vercel project...`);
// Extract project name from URL
const projectName = vercelUrl.replace('https://', '').split('-')[0];
// Add domain via Vercel API (if available)
// For now, we'll provide instructions
spinner.info('Please add the domain manually in Vercel dashboard');
console.log(chalk.yellow('\nš Manual steps required:'));
console.log(chalk.gray('1. Go to https://vercel.com/getaifactory/' + projectName + '/settings/domains'));
console.log(chalk.gray(`2. Click "Add Domain"`));
console.log(chalk.gray(`3. Enter: ${subdomain}.pame.ai`));
console.log(chalk.gray('4. Copy the DNS records shown\n'));
// Step 3: Get DNS records from user
const { hasDNSRecords } = await inquirer.prompt([
{
type: 'confirm',
name: 'hasDNSRecords',
message: 'Have you copied the DNS records from Vercel?',
default: false
}
]);
if (!hasDNSRecords) {
console.log(chalk.red('Please complete the Vercel setup first.'));
return;
}
// Get DNS records
const dnsAnswers = await inquirer.prompt([
{
type: 'input',
name: 'cnameValue',
message: 'Enter the CNAME value (e.g., 8af789697b12dc37.vercel-dns-016.com):',
validate: (input) => {
return input.includes('vercel-dns') || 'Please enter a valid Vercel DNS value';
}
},
{
type: 'input',
name: 'txtValue',
message: 'Enter the TXT verification value (e.g., vc-domain-verify=...):',
validate: (input) => {
return input.includes('vc-domain-verify') || 'Please enter a valid verification value';
}
}
]);
// Step 4: Add DNS records to GoDaddy
spinner.start('Adding DNS records to GoDaddy...');
const records = [
{
type: 'CNAME',
name: subdomain,
data: dnsAnswers.cnameValue,
ttl: 600
},
{
type: 'TXT',
name: '_vercel',
data: dnsAnswers.txtValue,
ttl: 600
}
];
try {
await godaddy.addRecords(records);
spinner.succeed('DNS records added successfully!');
console.log(chalk.green('\nā
Setup complete!'));
console.log(chalk.blue('\nš DNS Records Added:'));
records.forEach(record => {
console.log(chalk.gray(` ${record.type} ${record.name} ā ${record.data}`));
});
console.log(chalk.yellow('\nā±ļø Next steps:'));
console.log(chalk.gray('1. Wait 5-15 minutes for DNS propagation'));
console.log(chalk.gray('2. Click "Refresh" in Vercel domain settings'));
console.log(chalk.gray(`3. Your subdomain will be live at https://${subdomain}.pame.ai`));
}
catch (error) {
spinner.fail('Failed to add DNS records');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
}
catch (error) {
spinner.fail('Setup failed');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
dns
.command('list')
.description('List all DNS records for pame.ai')
.action(async () => {
const spinner = ora('Fetching DNS records...').start();
try {
const godaddy = new GoDaddyManager();
const records = await godaddy.getAllRecords();
spinner.succeed(`Found ${records.length} DNS records`);
// Group by type
const grouped = records.reduce((acc, record) => {
if (!acc[record.type])
acc[record.type] = [];
acc[record.type].push(record);
return acc;
}, {});
// Display records
Object.entries(grouped).forEach(([type, typeRecords]) => {
console.log(chalk.cyan(`\n${type} Records:`));
typeRecords.forEach(record => {
const name = record.name === '@' ? 'pame.ai' : `${record.name}.pame.ai`;
console.log(chalk.gray(` ${name} ā ${record.data} (TTL: ${record.ttl}s)`));
});
});
}
catch (error) {
spinner.fail('Failed to fetch DNS records');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
dns
.command('verify <subdomain>')
.description('Check if a subdomain is properly configured')
.action(async (subdomain) => {
const spinner = ora('Checking DNS configuration...').start();
try {
const godaddy = new GoDaddyManager();
const records = await godaddy.getAllRecords();
// Check for CNAME record
const cnameRecord = records.find(r => r.type === 'CNAME' && r.name === subdomain);
// Check for TXT verification record
const txtRecord = records.find(r => r.type === 'TXT' &&
r.name === '_vercel' &&
r.data.includes(`${subdomain}.pame.ai`));
spinner.stop();
console.log(chalk.cyan(`\nš DNS Status for ${subdomain}.pame.ai:\n`));
if (cnameRecord) {
console.log(chalk.green('ā
CNAME Record Found:'));
console.log(chalk.gray(` ${subdomain} ā ${cnameRecord.data}`));
}
else {
console.log(chalk.red('ā CNAME Record Missing'));
}
if (txtRecord) {
console.log(chalk.green('ā
TXT Verification Record Found:'));
console.log(chalk.gray(` _vercel ā ${txtRecord.data}`));
}
else {
console.log(chalk.yellow('ā ļø TXT Verification Record Missing'));
console.log(chalk.gray(' (This may be normal if domain is already verified)'));
}
// Test DNS resolution
console.log(chalk.cyan('\nš DNS Resolution Test:'));
try {
const { execSync } = require('child_process');
const result = execSync(`nslookup ${subdomain}.pame.ai`, { encoding: 'utf8' });
if (result.includes('Non-authoritative answer')) {
console.log(chalk.green('ā
Domain resolves successfully'));
}
else {
console.log(chalk.yellow('ā ļø Domain resolution unclear'));
}
}
catch {
console.log(chalk.red('ā Domain does not resolve'));
}
// Test HTTPS
console.log(chalk.cyan('\nš HTTPS Test:'));
try {
const https = require('https');
await new Promise((resolve, reject) => {
https.get(`https://${subdomain}.pame.ai`, (res) => {
if (res.statusCode === 200 || res.statusCode === 308) {
console.log(chalk.green('ā
HTTPS is working'));
resolve(true);
}
else {
console.log(chalk.yellow(`ā ļø HTTP Status: ${res.statusCode}`));
resolve(false);
}
}).on('error', reject);
});
}
catch (error) {
if (error.code === 'ENOTFOUND') {
console.log(chalk.red('ā Domain not found (DNS may still be propagating)'));
}
else {
console.log(chalk.red('ā HTTPS connection failed'));
}
}
}
catch (error) {
spinner.fail('Verification failed');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
return dns;
}
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
//# sourceMappingURL=dns.js.map