diff --git a/packages/atxp/src/call-tool.ts b/packages/atxp/src/call-tool.ts index 3cf5205..c23dd72 100644 --- a/packages/atxp/src/call-tool.ts +++ b/packages/atxp/src/call-tool.ts @@ -1,5 +1,6 @@ import { atxpClient, ATXPAccount } from '@atxp/client'; import chalk from 'chalk'; +import { getConnection } from './config.js'; export interface ToolResult { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>; @@ -10,7 +11,7 @@ export async function callTool( tool: string, args: Record ): Promise { - const connection = process.env.ATXP_CONNECTION; + const connection = getConnection(); if (!connection) { console.error(chalk.red('Not logged in.')); diff --git a/packages/atxp/src/config.ts b/packages/atxp/src/config.ts new file mode 100644 index 0000000..8277b01 --- /dev/null +++ b/packages/atxp/src/config.ts @@ -0,0 +1,110 @@ +import fs from 'fs'; +import path from 'path'; +import os from 'os'; + +export const CONFIG_DIR = path.join(os.homedir(), '.atxp'); +export const CONFIG_FILE = path.join(CONFIG_DIR, 'config'); + +/** + * Get the ATXP connection string. + * Checks env var first, falls back to reading from config file. + */ +export function getConnection(): string | null { + // Check env var first + if (process.env.ATXP_CONNECTION) { + return process.env.ATXP_CONNECTION; + } + + // Fall back to reading from config file + if (fs.existsSync(CONFIG_FILE)) { + try { + const content = fs.readFileSync(CONFIG_FILE, 'utf-8'); + // Parse: export ATXP_CONNECTION="..." + const match = content.match(/export ATXP_CONNECTION="(.+)"/); + if (match) { + return match[1]; + } + } catch { + // Ignore read errors + } + } + + return null; +} + +/** + * Save the connection string to the config file. + */ +export function saveConnection(connectionString: string): void { + if (!fs.existsSync(CONFIG_DIR)) { + fs.mkdirSync(CONFIG_DIR, { recursive: true }); + } + + const configContent = `export ATXP_CONNECTION="${connectionString}" +`; + fs.writeFileSync(CONFIG_FILE, configContent, { mode: 0o600 }); +} + +/** + * Detect the user's shell profile path. + * Returns null if shell cannot be detected or is unsupported. + */ +export function getShellProfile(): string | null { + const shell = process.env.SHELL || ''; + const home = os.homedir(); + + if (shell.includes('zsh')) { + return path.join(home, '.zshrc'); + } + + if (shell.includes('bash')) { + // On macOS, use .bash_profile; on Linux, use .bashrc + if (process.platform === 'darwin') { + return path.join(home, '.bash_profile'); + } + return path.join(home, '.bashrc'); + } + + // Fish uses different syntax, skip it + // Other shells are unsupported + return null; +} + +/** + * Add "source ~/.atxp/config" to the user's shell profile if not already present. + * Returns true if the profile was updated, false otherwise. + */ +export function updateShellProfile(): boolean { + const profilePath = getShellProfile(); + if (!profilePath) { + return false; + } + + const sourceLine = `source ${CONFIG_FILE}`; + + try { + let profileContent = ''; + + if (fs.existsSync(profilePath)) { + profileContent = fs.readFileSync(profilePath, 'utf-8'); + // Check if already present + if (profileContent.includes(sourceLine)) { + return false; + } + } + + // Add the source line with a comment + const addition = ` +# ATXP CLI configuration +if [ -f "${CONFIG_FILE}" ]; then + ${sourceLine} +fi +`; + + fs.appendFileSync(profilePath, addition); + return true; + } catch { + // Silently fail - CLI will still work via config file read + return false; + } +} diff --git a/packages/atxp/src/login.ts b/packages/atxp/src/login.ts index b36c369..a8d93ad 100644 --- a/packages/atxp/src/login.ts +++ b/packages/atxp/src/login.ts @@ -1,10 +1,9 @@ import http from 'http'; import fs from 'fs'; -import path from 'path'; -import os from 'os'; import chalk from 'chalk'; import open from 'open'; import qrcode from 'qrcode-terminal'; +import { saveConnection, updateShellProfile, CONFIG_FILE, getShellProfile } from './config.js'; interface LoginOptions { force?: boolean; @@ -12,9 +11,6 @@ interface LoginOptions { qr?: boolean; } -const CONFIG_DIR = path.join(os.homedir(), '.atxp'); -const CONFIG_FILE = path.join(CONFIG_DIR, 'config'); - export async function login(options: LoginOptions = {}): Promise { // Check if already logged in const existingConnection = process.env.ATXP_CONNECTION; @@ -43,16 +39,37 @@ export async function login(options: LoginOptions = {}): Promise { connectionString = await loginWithBrowserOrQR(); } - saveConnectionString(connectionString); + saveConnection(connectionString); + + // Try to auto-update shell profile + const profileUpdated = updateShellProfile(); + const profilePath = getShellProfile(); 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}`)); + + if (profileUpdated && profilePath) { + console.log(); + console.log(`Added ATXP to ${chalk.cyan(profilePath)}`); + console.log('New terminal windows will automatically have access to ATXP tools.'); + console.log(); + console.log('To use ATXP in this terminal session, run:'); + console.log(chalk.cyan(` source ${CONFIG_FILE}`)); + } else if (profilePath) { + // Profile exists but wasn't updated (already has source line) + console.log(); + console.log("You're all set! New terminal windows will have access to ATXP tools."); + console.log(); + console.log('To use ATXP in this terminal session, run:'); + console.log(chalk.cyan(` source ${CONFIG_FILE}`)); + } else { + // Couldn't detect shell profile - provide manual instructions + console.log(); + console.log('To use ATXP tools in this terminal, run:'); + console.log(chalk.cyan(` source ${CONFIG_FILE}`)); + console.log(); + console.log('To persist this, add the above line to your shell profile.'); + } } catch (error) { console.error(chalk.red('Login failed:'), error instanceof Error ? error.message : error); process.exit(1); @@ -271,12 +288,3 @@ function getSuccessHTML(): string { `; } -function saveConnectionString(connectionString: string): void { - if (!fs.existsSync(CONFIG_DIR)) { - fs.mkdirSync(CONFIG_DIR, { recursive: true }); - } - - const configContent = `export ATXP_CONNECTION="${connectionString}" -`; - fs.writeFileSync(CONFIG_FILE, configContent, { mode: 0o600 }); -}