@snapcommit/cli
Version:
Instant AI commits. Beautiful progress tracking. Never write commit messages again.
239 lines (238 loc) • 11.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isAuthenticated = isAuthenticated;
exports.getAuthConfig = getAuthConfig;
exports.clearAuth = clearAuth;
exports.promptAuth = promptAuth;
exports.ensureAuth = ensureAuth;
exports.getToken = getToken;
exports.logout = logout;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const os_1 = __importDefault(require("os"));
const chalk_1 = __importDefault(require("chalk"));
const readline_1 = __importDefault(require("readline"));
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.snapcommit');
const AUTH_FILE = path_1.default.join(CONFIG_DIR, 'auth.json');
// API URL - defaults to production, can be overridden for development
const API_BASE_URL = process.env.SNAPCOMMIT_API_URL || 'https://www.snapcommit.dev';
// Token validation cache
let lastValidationTime = 0;
const VALIDATION_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds
/**
* Ensure config directory exists
*/
function ensureConfigDir() {
if (!fs_1.default.existsSync(CONFIG_DIR)) {
fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
}
}
/**
* Check if user is authenticated
*/
function isAuthenticated() {
return fs_1.default.existsSync(AUTH_FILE);
}
/**
* Get stored authentication config
*/
function getAuthConfig() {
if (!fs_1.default.existsSync(AUTH_FILE)) {
return null;
}
try {
const data = fs_1.default.readFileSync(AUTH_FILE, 'utf-8');
return JSON.parse(data);
}
catch (error) {
return null;
}
}
/**
* Save authentication config
*/
function saveAuthConfig(config) {
ensureConfigDir();
fs_1.default.writeFileSync(AUTH_FILE, JSON.stringify(config, null, 2));
}
/**
* Clear authentication
*/
function clearAuth() {
if (fs_1.default.existsSync(AUTH_FILE)) {
fs_1.default.unlinkSync(AUTH_FILE);
}
}
/**
* Ask a question and return the answer
*/
function askQuestion(query) {
const rl = readline_1.default.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => rl.question(query, (ans) => {
rl.close();
resolve(ans);
}));
}
/**
* Verify token with backend
*/
async function verifyToken(token) {
try {
const response = await fetch(`${API_BASE_URL}/api/auth/token?token=${encodeURIComponent(token)}`);
const data = await response.json();
if (response.ok && data.valid) {
return { valid: true, user: data.user };
}
return { valid: false };
}
catch (error) {
throw new Error('Failed to verify token. Please check your internet connection.');
}
}
/**
* Prompt user to authenticate
*/
async function promptAuth() {
console.log(chalk_1.default.bold.cyan('\n╔════════════════════════════════════════╗'));
console.log(chalk_1.default.bold.cyan('║ 🔐 Authentication Required 🔐 ║'));
console.log(chalk_1.default.bold.cyan('╚════════════════════════════════════════╝\n'));
console.log(chalk_1.default.gray('SnapCommit needs authentication to provide AI-powered features.\n'));
console.log(chalk_1.default.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
console.log(chalk_1.default.bold.white('Step 1: ') + chalk_1.default.gray('Sign in with GitHub (one-click!)'));
console.log(chalk_1.default.cyan(' 👉 https://www.snapcommit.dev/login\n'));
console.log(chalk_1.default.bold.white('Step 2: ') + chalk_1.default.gray('Subscribe ($9.99/mo or $100/year)'));
console.log(chalk_1.default.cyan(' 👉 Choose your plan on the dashboard\n'));
console.log(chalk_1.default.bold.white('Step 3: ') + chalk_1.default.gray('Generate & paste your CLI token'));
console.log(chalk_1.default.cyan(' 👉 https://www.snapcommit.dev/dashboard\n'));
console.log(chalk_1.default.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
const token = await askQuestion(chalk_1.default.yellow('🔑 Paste your authentication token: '));
if (!token || token.trim().length === 0) {
console.log(chalk_1.default.red('\n❌ No token provided. Authentication cancelled.\n'));
console.log(chalk_1.default.gray('Run "snap" again when you\'re ready to authenticate.\n'));
return false;
}
console.log(chalk_1.default.gray('\n🔄 Verifying token with SnapCommit servers...\n'));
try {
const result = await verifyToken(token.trim());
if (result.valid && result.user) {
const config = {
token: token.trim(),
userId: result.user.id,
email: result.user.email,
name: result.user.name || 'Developer',
};
saveAuthConfig(config);
console.log(chalk_1.default.green.bold('✅ AUTHENTICATION SUCCESSFUL! 🎉\n'));
console.log(chalk_1.default.white(` Welcome back, ${chalk_1.default.bold(config.name)}!`));
console.log(chalk_1.default.gray(` Email: ${config.email}`));
console.log(chalk_1.default.gray(` Plan: SnapCommit Pro (Active)\n`));
console.log(chalk_1.default.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
console.log(chalk_1.default.bold('🚀 Quick Start:\n'));
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('snap quick') + chalk_1.default.gray(' - AI commit in any repo'));
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('snap') + chalk_1.default.gray(' - Natural language Git commands'));
console.log(chalk_1.default.gray(' • Type ') + chalk_1.default.cyan('exit') + chalk_1.default.gray(' to leave SnapCommit\n'));
console.log(chalk_1.default.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
console.log(chalk_1.default.bold('💡 Want GitHub features too?\n'));
console.log(chalk_1.default.gray(' Create PRs, check CI, and more with natural language!'));
console.log(chalk_1.default.gray(' Run: ') + chalk_1.default.cyan('github connect') + chalk_1.default.gray(' (inside snap)'));
console.log(chalk_1.default.gray(' (You\'ll need a GitHub Personal Access Token - Classic)\n'));
console.log(chalk_1.default.gray(' Get one at: ') + chalk_1.default.cyan('https://github.com/settings/tokens'));
console.log(chalk_1.default.gray(' Scopes needed: ') + chalk_1.default.white('repo, workflow, read:user'));
console.log(chalk_1.default.gray(' 💡 Tip: Set no expiration or remember to renew!\n'));
return true;
}
else {
console.log(chalk_1.default.red('\n❌ Invalid token. Please try again.\n'));
console.log(chalk_1.default.yellow('Troubleshooting:'));
console.log(chalk_1.default.gray(' • Make sure you copied the ENTIRE token'));
console.log(chalk_1.default.gray(' • Generate a new token at: https://www.snapcommit.dev/dashboard'));
console.log(chalk_1.default.gray(' • Ensure you have an active SnapCommit subscription\n'));
return false;
}
}
catch (error) {
console.log(chalk_1.default.red(`\n❌ ${error.message}\n`));
console.log(chalk_1.default.gray('Please check your internet connection and try again.\n'));
return false;
}
}
/**
* Ensure user is authenticated (prompt if not)
*/
async function ensureAuth() {
// Check if we have a locally stored token
if (isAuthenticated()) {
const config = getAuthConfig();
if (config) {
const now = Date.now();
const timeSinceLastValidation = now - lastValidationTime;
// Only validate if more than 1 hour has passed since last validation
if (timeSinceLastValidation > VALIDATION_INTERVAL) {
// CRITICAL: Verify token with server every hour
try {
const result = await verifyToken(config.token);
if (result.valid) {
// Token is still valid!
lastValidationTime = now; // Update last validation time
return config;
}
else {
// Token is invalid (user deleted, subscription expired, etc.)
console.log(chalk_1.default.yellow('\n⚠️ Your authentication token is no longer valid.'));
console.log(chalk_1.default.gray('This could mean:'));
console.log(chalk_1.default.gray(' • Your subscription has expired'));
console.log(chalk_1.default.gray(' • Your account was deleted'));
console.log(chalk_1.default.gray(' • The token was revoked\n'));
clearAuth();
lastValidationTime = 0; // Reset validation timer
// Fall through to re-prompt
}
}
catch (error) {
// Network error - allow offline usage with cached token
console.log(chalk_1.default.yellow('\n⚠️ Could not verify token (offline mode)'));
console.log(chalk_1.default.gray('Using cached credentials. Some features may be limited.\n'));
return config;
}
}
else {
// Token was validated recently (within the last hour) - skip validation
return config;
}
}
}
// No valid token - prompt for authentication
const success = await promptAuth();
if (success) {
lastValidationTime = Date.now(); // Set validation time after successful auth
return getAuthConfig();
}
return null;
}
/**
* Get authentication token for API calls
*/
function getToken() {
const config = getAuthConfig();
return config?.token || null;
}
/**
* Logout command
*/
async function logout() {
if (!isAuthenticated()) {
console.log(chalk_1.default.gray('\n Not currently logged in.\n'));
return;
}
const config = getAuthConfig();
console.log(chalk_1.default.yellow(`\n Logging out ${config?.email}...\n`));
clearAuth();
console.log(chalk_1.default.green(' ✅ Logged out successfully!\n'));
console.log(chalk_1.default.gray(' Run "snapcommit" again to log back in.\n'));
}