#!/usr/bin/env node import { Command } from 'commander'; import { AppState, Config } from './types.js'; import { TuiEngine } from './tui.js'; import { validateConnection, normalizeUrl } from './api.js'; import { loadSavedConfig, saveGlobalConfig } from './config.js'; import chalk from 'chalk'; const program = new Command(); program .name('gitea-tui') .description('A premium CLI TUI Dashboard for exploring Gitea and Forgejo issues') .version('1.0.0') .option('-u, --url ', 'Forgejo/Gitea instance base URL (e.g. https://forgejo.freshbrewed.science)') .option('-i, --userid ', 'Forgejo/Gitea user ID / username') .option('-r, --repo ', 'Repository path (e.g. owner/repo)') .option('-t, --token ', 'Personal Access Token (optional for public repositories)') .option('-s, --save', 'Save the provided settings to the global config file (~/.config/fjtui/fjtui.json)'); program.parse(process.argv); const options = program.opts(); async function bootstrap() { const savedConfig = loadSavedConfig(); const url = options.url || savedConfig.url || ''; const userid = options.userid || savedConfig.userid || ''; const token = options.token || savedConfig.token || ''; const repo = options.repo || savedConfig.repo || ''; // Initialize default state const state: AppState = { screen: 'setup', config: { url: '', token: null, userid: '', owner: '', repo: '', }, issues: [], currentPage: 1, issuesPerPage: 15, // matches standard screen sizes perfectly totalIssuesCount: 0, loading: false, error: null, selectedIssueIndex: 0, searchQuery: '', stateFilter: 'open', typeFilter: 'all', sortField: 'created', sortOrder: 'desc', selectedIssue: null, selectedIssueComments: [], commentsLoading: false, detailScrollOffset: 0, repos: [], selectedRepoIndex: 0, repoSearchQuery: '', repoPickerActiveSearch: false, setupForm: { url: url || 'https://forgejo.freshbrewed.science', userid: userid || '', token: token || '', saveConfig: true, activeField: url ? (userid ? 'token' : 'userid') : 'url', }, createIssueForm: { title: '', body: '', activeField: 'title', }, addCommentForm: { body: '', }, editIssueForm: { title: '', body: '', activeField: 'title', }, addTimeForm: { timeInput: '', }, }; // If parameters are provided, try direct connection first if (url && repo) { const repoParts = repo.split('/'); if (repoParts.length === 2 && repoParts[0].trim() && repoParts[1].trim()) { const normalized = normalizeUrl(url); const config: Config = { url: normalized, token: token ? token.trim() : null, userid: userid || repoParts[0].trim(), owner: repoParts[0].trim(), repo: repoParts[1].trim(), }; console.log(chalk.cyan(`Connecting to Gitea/Forgejo instance at ${normalized}...`)); try { await validateConnection(config); // Valid connection! Go straight to list screen state.config = config; state.screen = 'list'; // Optionally save to global config if --save option is provided if (options.save) { saveGlobalConfig({ url: normalized, userid: config.userid, token: config.token, repo: repo, }); console.log(chalk.green('Settings saved successfully to global config file!')); } } catch (err: any) { // Validation failed, let's load setup form with the entered values and show error! state.error = `Connection failed: ${err.message}`; state.screen = 'setup'; state.setupForm = { url: url, userid: userid || '', token: token || '', saveConfig: true, activeField: 'url', }; } } else { state.error = 'Invalid repository path. Must be in owner/repo format.'; state.screen = 'setup'; state.setupForm = { url: url, userid: userid || '', token: token || '', saveConfig: true, activeField: 'url', }; } } // Create and start the TUI engine const engine = new TuiEngine(state); engine.start(() => { // Perform cleanup and exit cleanly console.log(chalk.bold.green('\nThank you for using Forgejo TUI Issue Explorer! Goodbye.')); process.exit(0); }); } bootstrap().catch((err) => { console.error(chalk.bold.red('Fatal Error on Bootstrap:'), err); process.exit(1); });