Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 109 additions & 31 deletions packages/atxp/src/login.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import http from 'http';
import fs from 'fs';
import path from 'path';
import os from 'os';
import inquirer from 'inquirer';
import chalk from 'chalk';
import open from 'open';

interface LoginOptions {
force?: boolean;
Expand All @@ -23,41 +24,118 @@ export async function login(options: LoginOptions = {}): Promise<void> {

console.log(chalk.bold('ATXP Login'));
console.log();
console.log(`Get your connection string from: ${chalk.cyan('https://accounts.atxp.ai')}`);
console.log();

const { connectionString } = await inquirer.prompt([
{
type: 'password',
name: 'connectionString',
message: 'Enter your connection string:',
mask: '*',
validate: (input: string) => {
if (!input || input.trim().length === 0) {
return 'Connection string is required';
}
return true;
},
},
]);

// Create config directory if it doesn't exist
try {
const connectionString = await loginWithBrowser();
saveConnectionString(connectionString);

console.log();
console.log(chalk.green('Login successful!'));
console.log();
console.log('To use ATXP tools in this terminal, run:');
console.log(chalk.cyan(` source ${CONFIG_FILE}`));
console.log();
console.log('Or add this to your shell profile (~/.bashrc, ~/.zshrc, etc.):');
console.log(chalk.cyan(` source ${CONFIG_FILE}`));
} catch (error) {
console.error(chalk.red('Login failed:'), error instanceof Error ? error.message : error);
process.exit(1);
}
}

async function loginWithBrowser(): Promise<string> {
return new Promise((resolve, reject) => {
const server = http.createServer((req, res) => {
const url = new URL(req.url!, `http://localhost`);
const connectionString = url.searchParams.get('connection_string');

if (connectionString) {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<!DOCTYPE html>
<html>
<head>
<title>ATXP Login</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: #f5f5f5;
}
.container {
text-align: center;
padding: 40px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 { color: #10b981; margin-bottom: 16px; }
p { color: #666; }
</style>
</head>
<body>
<div class="container">
<h1>Login Successful</h1>
<p>You can close this tab and return to your terminal.</p>
</div>
</body>
</html>
`);
server.close();
resolve(decodeURIComponent(connectionString));
} else {
res.writeHead(400, { 'Content-Type': 'text/plain' });
res.end('Missing connection_string parameter');
}
});

server.on('error', (err) => {
reject(new Error(`Failed to start local server: ${err.message}`));
});

// Listen on random available port
server.listen(0, '127.0.0.1', () => {
const address = server.address();
const port = typeof address === 'object' ? address?.port : null;

if (!port) {
reject(new Error('Failed to start local server'));
return;
}

const redirectUri = `http://localhost:${port}/callback`;
const loginUrl = `https://accounts.atxp.ai?cli_redirect=${encodeURIComponent(redirectUri)}`;

console.log('Opening browser to complete login...');
console.log(chalk.gray(`(${loginUrl})`));
console.log();
console.log('Waiting for authentication...');

open(loginUrl);

// Timeout after 5 minutes
const timeout = setTimeout(() => {
server.close();
reject(new Error('Login timed out. Please try again.'));
}, 5 * 60 * 1000);

server.on('close', () => {
clearTimeout(timeout);
});
});
});
}

function saveConnectionString(connectionString: string): void {
if (!fs.existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR, { recursive: true });
}

// Write config file as shell export
const configContent = `export ATXP_CONNECTION="${connectionString.trim()}"
const configContent = `export ATXP_CONNECTION="${connectionString}"
`;

fs.writeFileSync(CONFIG_FILE, configContent, { mode: 0o600 });

console.log();
console.log(chalk.green('Login successful!'));
console.log();
console.log('To use ATXP tools in this terminal, run:');
console.log(chalk.cyan(` source ${CONFIG_FILE}`));
console.log();
console.log('Or add this to your shell profile (~/.bashrc, ~/.zshrc, etc.):');
console.log(chalk.cyan(` source ${CONFIG_FILE}`));
}