chromium-helper
Version:
CLI tool for searching and exploring Chromium source code via Google's official APIs
715 lines ⢠29.1 kB
JavaScript
import { Command } from 'commander';
import { ChromiumAPI } from './api.js';
import { formatOutput } from './formatter.js';
import { loadConfig } from './config.js';
import { getAIUsageGuide } from './ai-guide.js';
import { AuthManager, getAuthCookies } from './auth.js';
import { showCookieHelp } from './cookie-helper.js';
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import chalk from 'chalk';
const __filename = fileURLToPath(import.meta.url);
const packageRoot = path.resolve(path.dirname(__filename), '..');
const packageJsonPath = path.join(packageRoot, 'package.json');
let packageInfo;
try {
packageInfo = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
}
catch (error) {
packageInfo = { version: "1.0.0", name: "chromium-helper" };
}
const program = new Command();
async function main() {
const config = await loadConfig();
const api = new ChromiumAPI(config.apiKey);
program
.name('chromium-helper')
.alias('ch')
.description('CLI tool for searching and exploring Chromium source code')
.version(packageInfo.version)
.option('-f, --format <type>', 'output format (json|table|plain)', 'plain')
.option('--no-color', 'disable colored output')
.option('--debug', 'enable debug logging')
.option('--ai', 'show comprehensive usage guide for AI systems');
// Handle --ai flag
if (process.argv.includes('--ai')) {
console.log(getAIUsageGuide());
process.exit(0);
}
// Auth commands
const auth = program
.command('auth')
.description('Authentication management for Gerrit');
auth
.command('login')
.description('Authenticate with Gerrit using browser')
.option('--headless', 'Run browser in headless mode')
.action(async (options) => {
try {
const authManager = new AuthManager();
await authManager.authenticate({ headless: options.headless });
process.exit(0);
}
catch (error) {
console.error('Authentication failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
auth
.command('status')
.description('Check authentication status')
.action(async () => {
try {
const authManager = new AuthManager();
const isValid = await authManager.checkAuth();
if (isValid) {
console.log('ā
Authentication is valid');
}
else {
console.log('ā No valid authentication found');
console.log('Run: ch auth login');
}
process.exit(0);
}
catch (error) {
console.error('Status check failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
auth
.command('logout')
.description('Clear saved authentication')
.action(async () => {
try {
const authManager = new AuthManager();
await authManager.clearCookies();
process.exit(0);
}
catch (error) {
console.error('Logout failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
auth
.command('help')
.description('Show detailed help for getting authentication cookies')
.action(async () => {
await showCookieHelp();
process.exit(0);
});
auth
.command('manual')
.description('Manually save authentication cookies')
.action(async () => {
console.log(chalk.cyan('šŖ Manual Cookie Setup\n'));
console.log('Please follow these steps:');
console.log(chalk.yellow('\n1. Open Chrome and sign in to:'));
console.log(chalk.blue(' https://chromium-review.googlesource.com'));
console.log(chalk.yellow('\n2. Open Developer Tools (F12)'));
console.log(chalk.yellow('3. Go to Application > Cookies'));
console.log(chalk.yellow('4. Find ONE of these cookies:'));
console.log(chalk.green(' - __Secure-1PSID (recommended)'));
console.log(chalk.green(' - __Secure-3PSID (alternative)'));
console.log(chalk.gray('\n Note: You only need ONE of these cookies!'));
console.log(chalk.yellow('\n5. Enter the cookie value below:\n'));
// Use readline to get cookie values
const readline = await import('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const question = (prompt) => {
return new Promise((resolve) => {
rl.question(prompt, resolve);
});
};
try {
console.log(chalk.cyan('Which cookie do you have?'));
console.log('1. __Secure-1PSID');
console.log('2. __Secure-3PSID');
const choice = await question('\nEnter your choice (1 or 2): ');
let cookieString = '';
if (choice === '1') {
const psid1 = await question('__Secure-1PSID value: ');
if (!psid1) {
console.log(chalk.red('\nā Cookie value is required'));
process.exit(1);
}
cookieString = `__Secure-1PSID=${psid1}`;
}
else if (choice === '2') {
const psid3 = await question('__Secure-3PSID value: ');
if (!psid3) {
console.log(chalk.red('\nā Cookie value is required'));
process.exit(1);
}
cookieString = `__Secure-3PSID=${psid3}`;
}
else {
console.log(chalk.red('\nā Invalid choice. Please run the command again.'));
process.exit(1);
}
const authManager = new AuthManager();
await authManager.saveCookies(cookieString);
console.log(chalk.green('\nā
Cookies saved successfully!'));
console.log(chalk.gray('You can now use gerrit list commands without --auth-cookie parameter'));
rl.close();
process.exit(0);
}
catch (error) {
console.error('\nā Error saving cookies:', error);
rl.close();
process.exit(1);
}
});
// Search commands
program
.command('search')
.alias('s')
.description('Search Chromium source code')
.argument('<query>', 'search query')
.option('-c, --case-sensitive', 'case sensitive search')
.option('-l, --language <lang>', 'filter by programming language')
.option('-p, --file-pattern <pattern>', 'file pattern filter')
.option('-t, --type <type>', 'search type (content|function|class|symbol|comment)')
.option('--exclude-comments', 'exclude comments from search')
.option('--limit <number>', 'maximum number of results', '20')
.action(async (query, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.searchCode({
query,
caseSensitive: options.caseSensitive,
language: options.language,
filePattern: options.filePattern,
searchType: options.type,
excludeComments: options.excludeComments,
limit: parseInt(options.limit)
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'search'));
}
catch (error) {
console.error('Search failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// Symbol lookup command
program
.command('symbol')
.alias('sym')
.description('Find symbol definitions and usage')
.argument('<symbol>', 'symbol to find')
.option('-f, --file <path>', 'file path context for symbol resolution')
.action(async (symbol, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.findSymbol(symbol, options.file);
const format = program.opts().format;
console.log(formatOutput(results, format, 'symbol'));
}
catch (error) {
console.error('Symbol lookup failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// File content command
program
.command('file')
.alias('f')
.description('Get file content from Chromium source')
.argument('<path>', 'file path in Chromium source')
.option('-s, --start <line>', 'starting line number')
.option('-e, --end <line>', 'ending line number')
.action(async (filePath, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getFile({
filePath,
lineStart: options.start ? parseInt(options.start) : undefined,
lineEnd: options.end ? parseInt(options.end) : undefined
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'file'));
}
catch (error) {
console.error('File fetch failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// Gerrit CL commands
const gerrit = program
.command('gerrit')
.alias('gr')
.description('Gerrit code review operations');
gerrit
.command('status')
.description('Get CL status and test results')
.argument('<cl>', 'CL number or URL')
.action(async (cl) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getGerritCLStatus(cl);
const format = program.opts().format;
console.log(formatOutput(results, format, 'gerrit-status'));
}
catch (error) {
console.error('Gerrit status failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
gerrit
.command('comments')
.description('Get CL review comments')
.argument('<cl>', 'CL number or URL')
.option('-p, --patchset <number>', 'specific patchset number')
.option('--no-resolved', 'exclude resolved comments')
.action(async (cl, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getGerritCLComments({
clNumber: cl,
patchset: options.patchset ? parseInt(options.patchset) : undefined,
includeResolved: options.resolved !== false
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'gerrit-comments'));
}
catch (error) {
console.error('Gerrit comments failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
gerrit
.command('diff')
.description('Get CL diff/changes')
.argument('<cl>', 'CL number or URL')
.option('-p, --patchset <number>', 'specific patchset number')
.option('-f, --file <path>', 'specific file path to get diff for')
.action(async (cl, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getGerritCLDiff({
clNumber: cl,
patchset: options.patchset ? parseInt(options.patchset) : undefined,
filePath: options.file
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'gerrit-diff'));
}
catch (error) {
console.error('Gerrit diff failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
gerrit
.command('file')
.description('Get file content from CL patchset')
.argument('<cl>', 'CL number or URL')
.argument('<path>', 'file path to get content for')
.option('-p, --patchset <number>', 'specific patchset number')
.action(async (cl, filePath, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getGerritPatchsetFile({
clNumber: cl,
filePath,
patchset: options.patchset ? parseInt(options.patchset) : undefined
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'gerrit-file'));
}
catch (error) {
console.error('Gerrit file failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
gerrit
.command('bots')
.description('Get try-bot status for CL')
.argument('<cl>', 'CL number or URL')
.option('-p, --patchset <number>', 'specific patchset number')
.option('--failed-only', 'show only failed bots')
.action(async (cl, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getGerritCLTrybotStatus({
clNumber: cl,
patchset: options.patchset ? parseInt(options.patchset) : undefined,
failedOnly: options.failedOnly
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'gerrit-bots'));
}
catch (error) {
console.error('Gerrit bots failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
gerrit
.command('list')
.description('List Gerrit CLs (requires authentication)')
.option('-q, --query <query>', 'Gerrit search query (default: status:open owner:self)')
.option('-a, --auth-cookie <cookie>', 'authentication cookie (optional if already logged in)')
.option('-l, --limit <number>', 'maximum number of CLs to return', '25')
.action(async (options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
// Get auth cookies with fallback to saved auth
let authCookie;
try {
authCookie = await getAuthCookies(options.authCookie);
}
catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
const results = await api.listGerritCLs({
query: options.query,
authCookie: authCookie,
limit: parseInt(options.limit)
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'gerrit-list'));
}
catch (error) {
console.error('Gerrit list failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// Owners command
program
.command('owners')
.alias('own')
.description('Find OWNERS files for a file path')
.argument('<path>', 'file path to find owners for')
.action(async (filePath) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.findOwners(filePath);
const format = program.opts().format;
console.log(formatOutput(results, format, 'owners'));
}
catch (error) {
console.error('Owners lookup failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// Commits search command
program
.command('commits')
.alias('cm')
.description('Search commit history')
.argument('<query>', 'search query for commits')
.option('-a, --author <author>', 'filter by author')
.option('--since <date>', 'commits after date (YYYY-MM-DD)')
.option('--until <date>', 'commits before date (YYYY-MM-DD)')
.option('--limit <number>', 'maximum number of results', '20')
.action(async (query, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.searchCommits({
query,
author: options.author,
since: options.since,
until: options.until,
limit: parseInt(options.limit)
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'commits'));
}
catch (error) {
console.error('Commit search failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// Issue commands
const issues = program
.command('issues')
.alias('bugs')
.description('Chromium issue operations');
issues
.command('get')
.alias('show')
.description('Get Chromium issue details')
.argument('<id>', 'issue ID or URL')
.action(async (issueId) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getIssue(issueId);
const format = program.opts().format;
console.log(formatOutput(results, format, 'issue'));
}
catch (error) {
console.error('Issue lookup failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
issues
.command('search')
.alias('find')
.description('Search Chromium issues')
.argument('<query>', 'search query')
.option('--limit <number>', 'maximum number of results', '50')
.option('--start <number>', 'starting index for pagination', '0')
.action(async (query, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.searchIssues(query, {
limit: parseInt(options.limit),
startIndex: parseInt(options.start)
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'issue-search'));
}
catch (error) {
console.error('Issue search failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// Legacy issue command for backward compatibility
program
.command('issue')
.alias('bug')
.description('Get Chromium issue details')
.argument('<id>', 'issue ID or URL')
.action(async (issueId) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getIssue(issueId);
const format = program.opts().format;
console.log(formatOutput(results, format, 'issue'));
}
catch (error) {
console.error('Issue lookup failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// Direct issue search command for convenience
program
.command('issue-search')
.alias('isearch')
.description('Search Chromium issues (shortcut for issues search)')
.argument('<query>', 'search query')
.option('--limit <number>', 'maximum number of results', '50')
.option('--start <number>', 'starting index for pagination', '0')
.action(async (query, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.searchIssues(query, {
limit: parseInt(options.limit),
startIndex: parseInt(options.start)
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'issue-search'));
}
catch (error) {
console.error('Issue search failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// PDFium Gerrit commands
const pdfium = program
.command('pdfium')
.alias('pdf')
.description('PDFium Gerrit operations');
pdfium
.command('status')
.description('Get PDFium CL status and test results')
.argument('<cl>', 'CL number or URL')
.action(async (cl) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getPdfiumGerritCLStatus(cl);
const format = program.opts().format;
console.log(formatOutput(results, format, 'gerrit-status'));
}
catch (error) {
console.error('PDFium Gerrit status failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
pdfium
.command('comments')
.description('Get PDFium CL review comments')
.argument('<cl>', 'CL number or URL')
.option('-p, --patchset <number>', 'specific patchset number')
.option('--no-resolved', 'exclude resolved comments')
.action(async (cl, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getPdfiumGerritCLComments({
clNumber: cl,
patchset: options.patchset ? parseInt(options.patchset) : undefined,
includeResolved: options.resolved !== false
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'gerrit-comments'));
}
catch (error) {
console.error('PDFium Gerrit comments failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
pdfium
.command('diff')
.description('Get PDFium CL diff/changes')
.argument('<cl>', 'CL number or URL')
.option('-p, --patchset <number>', 'specific patchset number')
.option('-f, --file <path>', 'specific file path to get diff for')
.action(async (cl, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getPdfiumGerritCLDiff({
clNumber: cl,
patchset: options.patchset ? parseInt(options.patchset) : undefined,
filePath: options.file
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'gerrit-diff'));
}
catch (error) {
console.error('PDFium Gerrit diff failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
pdfium
.command('file')
.description('Get file content from PDFium CL patchset')
.argument('<cl>', 'CL number or URL')
.argument('<path>', 'file path to get content for')
.option('-p, --patchset <number>', 'specific patchset number')
.action(async (cl, filePath, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getPdfiumGerritPatchsetFile({
clNumber: cl,
filePath,
patchset: options.patchset ? parseInt(options.patchset) : undefined
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'gerrit-file'));
}
catch (error) {
console.error('PDFium Gerrit file failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
pdfium
.command('bots')
.description('Get try-bot status for PDFium CL')
.argument('<cl>', 'CL number or URL')
.option('-p, --patchset <number>', 'specific patchset number')
.option('--failed-only', 'show only failed bots')
.action(async (cl, options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.getPdfiumGerritCLTrybotStatus({
clNumber: cl,
patchset: options.patchset ? parseInt(options.patchset) : undefined,
failedOnly: options.failedOnly
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'gerrit-bots'));
}
catch (error) {
console.error('PDFium Gerrit bots failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
pdfium
.command('list')
.description('List PDFium Gerrit CLs (requires authentication)')
.option('-q, --query <query>', 'PDFium Gerrit search query (default: status:open owner:self)')
.option('-a, --auth-cookie <cookie>', 'authentication cookie (optional if already logged in)')
.option('-l, --limit <number>', 'maximum number of CLs to return', '25')
.action(async (options) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
// Get auth cookies with fallback to saved auth
let authCookie;
try {
authCookie = await getAuthCookies(options.authCookie);
}
catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
const results = await api.listPdfiumGerritCLs({
query: options.query,
authCookie: authCookie,
limit: parseInt(options.limit)
});
const format = program.opts().format;
console.log(formatOutput(results, format, 'pdfium-gerrit-list'));
}
catch (error) {
console.error('PDFium Gerrit list failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// List folder command
program
.command('list-folder')
.alias('ls')
.description('List files and folders in a Chromium source directory')
.argument('<path>', 'folder path in Chromium source')
.action(async (folderPath) => {
try {
const globalOptions = program.opts();
api.setDebugMode(globalOptions.debug);
const results = await api.listFolder(folderPath);
const format = program.opts().format;
console.log(formatOutput(results, format, 'list-folder'));
}
catch (error) {
console.error('Folder listing failed:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// Config command
program
.command('config')
.description('Configuration management')
.option('--set-api-key <key>', 'set API key')
.option('--show', 'show current configuration')
.action(async (options) => {
if (options.setApiKey) {
// TODO: Implement config setting
console.log('API key configuration not yet implemented');
}
else if (options.show) {
console.log('Current configuration:');
console.log(`API Key: ${config.apiKey ? '***set***' : 'not set'}`);
}
});
await program.parseAsync(process.argv);
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});
//# sourceMappingURL=index.js.map