logggai
Version:
AI-powered CLI for transforming your development work into professional content
306 lines • 11.7 kB
JavaScript
#!/usr/bin/env node
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const login_1 = require("./commands/login");
const register_1 = require("./commands/register");
const logout_1 = require("./commands/logout");
const post_1 = require("./commands/post");
const list_1 = require("./commands/list");
const { configCommand } = require('./commands/config');
const context_1 = require("./commands/context");
const package_json_1 = require("../package.json");
const project_1 = require("./commands/project");
const sync_1 = require("./commands/sync");
const fs = require("fs");
const path = require("path");
const child_process_1 = require("child_process");
const token_1 = require("./commands/token");
const chalk_1 = require("chalk");
const auth_1 = require("./lib/auth");
const program = new commander_1.Command();
program
.name('logggai')
.description('AI-powered CLI for transforming your development work into professional content')
.version(package_json_1.version);
// === Auto-launch daemon if needed ===
function isDaemonRunning() {
const pidFile = path.join(process.cwd(), '.logggai-daemon.pid');
if (!fs.existsSync(pidFile))
return false;
try {
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8'));
process.kill(pid, 0); // throws if not running
return true;
}
catch {
return false;
}
}
function launchDaemonIfNeeded() {
const projectFile = path.join(process.cwd(), '.logggai-project.json');
if (!fs.existsSync(projectFile))
return;
if (isDaemonRunning())
return;
// Launch daemon in detached/background mode
const daemonPath = path.resolve(__dirname, 'autosync-daemon.js');
const child = (0, child_process_1.spawn)('node', [daemonPath], {
cwd: process.cwd(),
detached: true,
stdio: 'ignore',
});
child.unref();
console.log('Logggai auto-sync daemon started in background.');
}
// Only auto-launch if not running the daemon command itself
if (process.argv[2] !== 'daemon') {
launchDaemonIfNeeded();
}
// Helper to centralize automatic relogin and command relaunch after login
function withSessionCheck(fn) {
return async (...args) => {
// If ensureValidSession relaunches the command, stop current execution
const proceed = await (0, auth_1.ensureValidSession)(() => fn(...args));
if (!proceed)
return;
// Command already executed in ensureValidSession if proceed is true
};
}
// Available commands
program
.command('register')
.description('Create a new account')
.action(register_1.registerCommand);
program
.command('login')
.description('Login to your account')
.action(login_1.loginCommand);
program
.command('logout')
.description('Logout from your account')
.action(logout_1.logoutCommand);
program
.command('post <title>')
.description('Create a new article with AI enhancement')
.option('-c, --content <content>', 'Article content')
.option('-t, --tags <tags>', 'Tags separated by commas')
.option('--no-ai', 'Disable AI processing')
.option('-p, --prompt <promptId>', 'AI prompt ID to use')
.option('-o, --org <organization>', 'Create in specific organization (name, slug or ID)')
.action(post_1.postCommand);
program
.command('list')
.description('List your recent articles (current context)')
.option('-l, --limit <number>', 'Number of articles to display', '10')
.option('-o, --org <organization>', 'List from specific organization (name, slug or ID)')
.action(list_1.listCommand);
program
.command('config')
.description('Configure the CLI')
.option('-s, --set <key=value>', 'Set a configuration value')
.option('-g, --get <key>', 'Get a configuration value')
.option('-l, --list', 'List all configurations')
.action(configCommand);
// Context management commands
program
.command('context')
.description('Manage contexts (Personal/Organization)')
.option('-l, --list', 'List available contexts')
.option('-s, --switch <org>', 'Switch to organization')
.option('-p, --personal', 'Switch to personal context')
.action(context_1.contextCommand);
program
.command('switch [organization]')
.description('Quick context switch (personal if no argument)')
.action(context_1.switchCommand);
program
.command('use <context>')
.description('Use a context (alias for switch)')
.action(context_1.useCommand);
program
.command('project')
.description('Create or initialize a Logggai project (local/cloud mapping)')
.action(project_1.projectCommand);
program
.command('sync')
.description('Sync git commits with Logggai (AI, publishing, mapping)')
// TODO: Add options if needed
.action(sync_1.syncCommand);
program
.command('start')
.description('Start the Logggai auto-sync daemon in the current project directory.')
.action(() => {
// No session check needed here, the daemon will check itself
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
function isDaemonRunning() {
const pidFile = path.join(process.cwd(), '.logggai-daemon.pid');
if (!fs.existsSync(pidFile))
return false;
try {
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8'));
process.kill(pid, 0);
return true;
}
catch {
return false;
}
}
if (isDaemonRunning()) {
console.log('\x1b[33mLogggai auto-sync daemon is already running in this project.\x1b[0m');
return;
}
const daemonPath = path.resolve(__dirname, 'autosync-daemon.js');
const child = spawn('node', [daemonPath], {
cwd: process.cwd(),
detached: true,
stdio: 'ignore',
});
child.unref();
console.log('\x1b[32mLogggai auto-sync daemon started in background.\x1b[0m');
});
program
.command('status')
.description('Show the status of the Logggai auto-sync daemon in the current project directory.')
.action(() => {
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const pidFile = path.join(process.cwd(), '.logggai-daemon.pid');
const logFile = path.join(process.cwd(), '.logggai-daemon.log');
const projectFile = path.join(process.cwd(), '.logggai-project.json');
if (!fs.existsSync(projectFile)) {
console.log(chalk.yellow('No Logggai project found in this directory.'));
console.log(chalk.gray('This folder is not a Logggai project. Move to a mapped project folder or run: npx logggai project to initialize.'));
return;
}
if (fs.existsSync(pidFile)) {
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8'));
let running = false;
try {
process.kill(pid, 0); // throws if not running
running = true;
}
catch {
running = false;
}
if (running) {
console.log(chalk.green('Logggai auto-sync daemon is running.'));
console.log(chalk.gray(' PID:'), pid);
}
else {
console.log(chalk.red('Logggai auto-sync daemon PID file found, but process is not running.'));
console.log(chalk.gray(' PID:'), pid);
}
// Show last 10 lines of log if exists
if (fs.existsSync(logFile)) {
console.log(chalk.cyan('\nLast 10 lines of .logggai-daemon.log:'));
const lines = fs.readFileSync(logFile, 'utf-8').split('\n').filter(Boolean);
const lastLines = lines.slice(-10);
lastLines.forEach((line) => console.log(chalk.gray(' ' + line)));
}
}
else {
console.log(chalk.yellow('Logggai auto-sync daemon is not running in this project.'));
console.log(chalk.gray('Run: logggai start'));
}
});
program
.command('token')
.description('Show your Clerk session token for MCP/agent usage')
.action(withSessionCheck(token_1.tokenCommand));
program
.command('whoami')
.description('Show the current user, context, and token expiration')
.action(withSessionCheck(async () => {
const { getConfig, isLoggedIn, getCurrentContext, getCurrentOrganizationId } = require('./lib/config');
if (!isLoggedIn()) {
console.log(chalk_1.default.red('You are not logged in. Use: logggai login'));
return;
}
const email = getConfig('userEmail');
const context = getCurrentContext() || 'personal';
const orgId = getCurrentOrganizationId();
const token = getConfig('sessionToken');
let exp = null;
try {
if (token) {
const payload = token.split('.')[1];
const decoded = JSON.parse(Buffer.from(payload, 'base64').toString('utf8'));
if (decoded.exp) {
exp = new Date(decoded.exp * 1000);
}
}
}
catch { }
console.log(chalk_1.default.green('Logged in user:'));
if (email)
console.log(chalk_1.default.cyan(' Email:'), email);
console.log(chalk_1.default.cyan(' Context:'), context === 'organization' ? 'Organization' : 'Personal');
if (context === 'organization' && orgId)
console.log(chalk_1.default.cyan(' Organization ID:'), orgId);
if (exp) {
const now = new Date();
const diff = (exp.getTime() - now.getTime()) / (1000 * 60 * 60);
console.log(chalk_1.default.cyan(' Token expiration:'), exp.toISOString());
if (diff < 24 && diff > 0) {
console.log(chalk_1.default.yellow.bold('Warning: your token expires in less than 24h. Run logggai login soon.'));
}
else if (diff <= 0) {
console.log(chalk_1.default.red.bold('Token expired. Please run logggai login.'));
}
}
else {
console.log(chalk_1.default.gray(' (Token expiration unknown)'));
}
}));
program
.command('stop')
.description('Stop the Logggai auto-sync daemon in the current project directory.')
.action(() => {
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const pidFile = path.join(process.cwd(), '.logggai-daemon.pid');
const projectFile = path.join(process.cwd(), '.logggai-project.json');
if (!fs.existsSync(projectFile)) {
console.log(chalk.yellow('No Logggai project found in this directory.'));
console.log(chalk.gray('Run: logggai project'));
return;
}
if (!fs.existsSync(pidFile)) {
console.log(chalk.yellow('No daemon is running in this project.'));
return;
}
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8'));
let running = false;
try {
process.kill(pid, 0); // throws if not running
running = true;
}
catch {
running = false;
}
if (running) {
try {
process.kill(pid, 'SIGTERM');
fs.unlinkSync(pidFile);
console.log(chalk.green('Logggai auto-sync daemon stopped successfully.'));
console.log(chalk.gray(' PID:'), pid);
}
catch (err) {
const error = err;
console.log(chalk.red('Failed to stop daemon.'));
console.log(chalk.gray(' PID:'), pid);
console.log(chalk.gray('Error:'), error.message || error);
}
}
else {
fs.unlinkSync(pidFile);
console.log(chalk.yellow('Daemon process was not running, but PID file has been removed.'));
}
});
program.parse();
//# sourceMappingURL=index.js.map