fjtui/src/index.ts

176 lines
5.1 KiB
TypeScript
Raw Normal View History

2026-06-01 00:59:22 +00:00
#!/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';
2026-06-01 02:27:57 +00:00
import { loadSavedConfig, saveGlobalConfig } from './config.js';
2026-06-01 00:59:22 +00:00
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 <url>', 'Forgejo/Gitea instance base URL (e.g. https://forgejo.freshbrewed.science)')
2026-06-01 02:02:23 +00:00
.option('-i, --userid <userid>', 'Forgejo/Gitea user ID / username')
2026-06-01 00:59:22 +00:00
.option('-r, --repo <owner/repo>', 'Repository path (e.g. owner/repo)')
2026-06-01 02:27:57 +00:00
.option('-t, --token <token>', 'Personal Access Token (optional for public repositories)')
.option('-s, --save', 'Save the provided settings to the global config file (~/.config/fjtui/fjtui.json)');
2026-06-01 00:59:22 +00:00
program.parse(process.argv);
const options = program.opts();
async function bootstrap() {
2026-06-01 02:27:57 +00:00
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 || '';
2026-06-01 00:59:22 +00:00
// Initialize default state
const state: AppState = {
screen: 'setup',
2026-06-03 13:34:31 +00:00
previousScreen: 'list',
focusedPane: 'list',
selectedSettingIndex: 0,
2026-06-03 18:38:41 +00:00
autoRefreshInterval: savedConfig.autoRefreshInterval || 0,
2026-06-01 00:59:22 +00:00
config: {
url: '',
token: null,
2026-06-01 02:02:23 +00:00
userid: '',
2026-06-01 00:59:22 +00:00
owner: '',
repo: '',
2026-06-03 18:38:41 +00:00
autoRefreshInterval: savedConfig.autoRefreshInterval || 0,
2026-06-01 00:59:22 +00:00
},
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,
2026-06-02 18:41:33 +00:00
assigneesInput: '',
2026-06-01 02:02:23 +00:00
repos: [],
selectedRepoIndex: 0,
repoSearchQuery: '',
repoPickerActiveSearch: false,
2026-06-01 00:59:22 +00:00
setupForm: {
2026-06-01 02:27:57 +00:00
url: url || 'https://forgejo.freshbrewed.science',
userid: userid || '',
token: token || '',
saveConfig: true,
activeField: url ? (userid ? 'token' : 'userid') : 'url',
2026-06-01 00:59:22 +00:00
},
createIssueForm: {
title: '',
body: '',
activeField: 'title',
},
2026-06-01 03:18:12 +00:00
addCommentForm: {
body: '',
},
2026-06-01 23:15:01 +00:00
editIssueForm: {
title: '',
body: '',
activeField: 'title',
},
2026-06-01 23:34:20 +00:00
addTimeForm: {
timeInput: '',
},
2026-06-02 23:42:07 +00:00
labels: [],
selectedLabelIndex: 0,
labelsLoading: false,
labelForm: {
name: '',
color: '',
description: '',
exclusive: false,
activeField: 'name',
},
2026-06-01 00:59:22 +00:00
};
// If parameters are provided, try direct connection first
2026-06-01 02:27:57 +00:00
if (url && repo) {
const repoParts = repo.split('/');
2026-06-01 00:59:22 +00:00
if (repoParts.length === 2 && repoParts[0].trim() && repoParts[1].trim()) {
2026-06-01 02:27:57 +00:00
const normalized = normalizeUrl(url);
2026-06-01 00:59:22 +00:00
const config: Config = {
url: normalized,
2026-06-01 02:27:57 +00:00
token: token ? token.trim() : null,
userid: userid || repoParts[0].trim(),
2026-06-01 00:59:22 +00:00
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';
2026-06-01 02:27:57 +00:00
// Optionally save to global config if --save option is provided
if (options.save) {
saveGlobalConfig({
url: normalized,
userid: config.userid,
token: config.token,
repo: repo,
2026-06-03 18:38:41 +00:00
autoRefreshInterval: state.autoRefreshInterval,
2026-06-01 02:27:57 +00:00
});
console.log(chalk.green('Settings saved successfully to global config file!'));
}
2026-06-01 00:59:22 +00:00
} 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 = {
2026-06-01 02:27:57 +00:00
url: url,
userid: userid || '',
token: token || '',
saveConfig: true,
2026-06-01 00:59:22 +00:00
activeField: 'url',
};
}
} else {
2026-06-01 02:27:57 +00:00
state.error = 'Invalid repository path. Must be in owner/repo format.';
2026-06-01 00:59:22 +00:00
state.screen = 'setup';
2026-06-01 02:27:57 +00:00
state.setupForm = {
url: url,
userid: userid || '',
token: token || '',
saveConfig: true,
activeField: 'url',
};
2026-06-01 00:59:22 +00:00
}
}
// 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);
});