Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions scripts/test-cli.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail

# Ensure tests run in English locale
node dist/index.js lang en >/dev/null 2>&1 || true

version_output="$(node dist/index.js --version)"
if [[ ! "$version_output" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "version output not semver: $version_output" >&2
Expand Down
1 change: 0 additions & 1 deletion src/config/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export const APP_INFO = {
name: 'Prompt',
version: readPackageVersion(),
description: '浙大宁波理工学院计算机协会',
fullDescription: 'NBTCA Prompt - 极简命令行工具',
author: 'm1ngsama <contact@m1ng.space>',
license: 'MIT',
repository: 'https://github.com/nbtca/prompt'
Expand Down
1 change: 0 additions & 1 deletion src/core/vim-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

// Maps single-byte vim keys to terminal escape sequences (ranger-style hjkl)
const VIM_TO_SEQ: Record<string, Buffer> = {
h: Buffer.from('\u0003'), // back/cancel (ranger: go to parent)
j: Buffer.from('\u001b[B'), // down arrow
k: Buffer.from('\u001b[A'), // up arrow
l: Buffer.from('\r'), // enter/confirm (ranger: open/enter)
Expand Down
9 changes: 6 additions & 3 deletions src/features/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ function detectTerminalType(): TerminalType {
/** Check whether an external command exists on PATH (once at startup). */
function commandExists(cmd: string): boolean {
try {
execFileSync('which', [cmd], { stdio: 'ignore' });
const check = process.platform === 'win32' ? 'where' : 'which';
execFileSync(check, [cmd], { stdio: 'ignore' });
return true;
} catch {
return false;
Expand Down Expand Up @@ -578,7 +579,7 @@ async function viewMarkdownFile(filePath: string): Promise<void> {
while (true) {
try {
ensureMarkedConfigured();
const s = createSpinner(`${trans.docs.loading.replace('...', '')}: ${filePath}`);
const s = createSpinner(`${trans.docs.loadingFile}: ${filePath}`);

const rawResult = await fetchGitHubRawContent(filePath);
if (rawResult.staleFallback) {
Expand Down Expand Up @@ -706,7 +707,9 @@ async function searchDocs(): Promise<void> {
if (results.some(r => r.path === cachedPath)) continue;
if (entry.value.toLowerCase().includes(keyword)) {
const name = cachedPath.split('/').pop() || cachedPath;
results.push({ name, path: cachedPath, category: trans.docs.searchResults });
const parentDir = cachedPath.split('/').slice(0, -1).join('/');
const matchedCat = categories.find(c => parentDir.startsWith(c.path));
results.push({ name, path: cachedPath, category: matchedCat?.name ?? parentDir });
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/features/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function showAbout(): void {
const content = [
row(trans.about.project, APP_INFO.name),
row(trans.about.version, `v${APP_INFO.version}`),
row(trans.about.description, APP_INFO.fullDescription),
row(trans.about.description, trans.about.descriptionText),
'',
link(trans.about.github, APP_INFO.repository),
link(trans.about.website, URLS.homepage),
Expand Down
38 changes: 38 additions & 0 deletions src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface Translations {
project: string;
version: string;
description: string;
descriptionText: string;
github: string;
website: string;
email: string;
Expand Down Expand Up @@ -111,6 +112,7 @@ export interface Translations {
searching: string;
searchResults: string;
searchNoResults: string;
loadingFile: string;
};
links: {
choose: string;
Expand Down Expand Up @@ -181,6 +183,42 @@ export interface Translations {
checkFailed: string;
command: string;
};
cli: {
usage: string;
interactive: string;
runCommand: string;
commands: string;
flags: string;
cmdWebsite: string;
cmdGithub: string;
cmdRoadmap: string;
cmdRepair: string;
cmdTheme: string;
cmdLang: string;
cmdUpdate: string;
flagVersion: string;
flagHelp: string;
flagOpen: string;
flagJson: string;
flagToday: string;
flagNext: string;
flagWatch: string;
flagInterval: string;
flagTimeout: string;
flagRetries: string;
flagPlain: string;
flagNoLogo: string;
unknownCommand: string;
unknownCommandHint: string;
unknownFlag: string;
unknownFlagHint: string;
invalidFlag: string;
invalidFlagHint: string;
invalidLang: string;
invalidNext: string;
requiresTty: string;
requiresTtyHint: string;
};
}

/**
Expand Down
40 changes: 39 additions & 1 deletion src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"project": "Project",
"version": "Version",
"description": "Description",
"descriptionText": "NBTCA Prompt - Minimalist CLI Tool",
"github": "GitHub",
"website": "Website",
"email": "Email",
Expand Down Expand Up @@ -91,7 +92,8 @@
"searchPlaceholder": "Enter keyword...",
"searching": "Searching documents...",
"searchResults": "results found",
"searchNoResults": "No documents match your search"
"searchNoResults": "No documents match your search",
"loadingFile": "Loading"
},
"links": {
"choose": "Open a link:",
Expand Down Expand Up @@ -161,5 +163,41 @@
"upToDate": "You are on the latest version ({version})",
"checkFailed": "Could not check for updates",
"command": "Run: npm i -g @nbtca/prompt"
},
"cli": {
"usage": "Usage:",
"interactive": "Interactive menu",
"runCommand": "Run a command",
"commands": "Commands:",
"flags": "Flags:",
"cmdWebsite": "Official website URL",
"cmdGithub": "GitHub organization URL",
"cmdRoadmap": "Project roadmap URL",
"cmdRepair": "Repair service URL",
"cmdTheme": "View or set theme",
"cmdLang": "Set language",
"cmdUpdate": "Check for updates",
"flagVersion": "Show version",
"flagHelp": "Show help",
"flagOpen": "Open in browser (URL commands)",
"flagJson": "JSON output (events, status)",
"flagToday": "Today only (events)",
"flagNext": "Limit to next N (events)",
"flagWatch": "Live refresh (status)",
"flagInterval": "Refresh interval (status --watch)",
"flagTimeout": "HTTP timeout (status)",
"flagRetries": "Retry count (status)",
"flagPlain": "Disable colors",
"flagNoLogo": "Skip logo",
"unknownCommand": "Unknown command: {command}",
"unknownCommandHint": "Run `nbtca --help` to see available commands.",
"unknownFlag": "Unknown flag: {flag}",
"unknownFlagHint": "Run `nbtca --help` to see available flags.",
"invalidFlag": "Flag {flag} is not valid for this command.",
"invalidFlagHint": "Run `nbtca --help` to see command usage.",
"invalidLang": "Invalid language. Use `zh` or `en`.",
"invalidNext": "Invalid --next value. Use --next=<number> (>= 1).",
"requiresTty": "Interactive mode requires a TTY terminal.",
"requiresTtyHint": "Use `nbtca --help` for command mode."
}
}
40 changes: 39 additions & 1 deletion src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"project": "项目",
"version": "版本",
"description": "描述",
"descriptionText": "NBTCA Prompt - 极简命令行工具",
"github": "GitHub",
"website": "网站",
"email": "邮箱",
Expand Down Expand Up @@ -91,7 +92,8 @@
"searchPlaceholder": "输入关键词...",
"searching": "正在搜索文档...",
"searchResults": "个结果",
"searchNoResults": "未找到匹配的文档"
"searchNoResults": "未找到匹配的文档",
"loadingFile": "正在加载"
},
"links": {
"choose": "打开链接:",
Expand Down Expand Up @@ -161,5 +163,41 @@
"upToDate": "已是最新版本 ({version})",
"checkFailed": "无法检查更新",
"command": "运行: npm i -g @nbtca/prompt"
},
"cli": {
"usage": "用法:",
"interactive": "交互式菜单",
"runCommand": "运行命令",
"commands": "命令:",
"flags": "选项:",
"cmdWebsite": "官方网站 URL",
"cmdGithub": "GitHub 组织 URL",
"cmdRoadmap": "项目路线图 URL",
"cmdRepair": "维修服务 URL",
"cmdTheme": "查看或设置主题",
"cmdLang": "设置语言",
"cmdUpdate": "检查更新",
"flagVersion": "显示版本号",
"flagHelp": "显示帮助",
"flagOpen": "在浏览器中打开(URL 命令)",
"flagJson": "JSON 输出(events, status)",
"flagToday": "仅显示今日(events)",
"flagNext": "限制为前 N 个(events)",
"flagWatch": "实时刷新(status)",
"flagInterval": "刷新间隔(status --watch)",
"flagTimeout": "HTTP 超时时间(status)",
"flagRetries": "重试次数(status)",
"flagPlain": "禁用颜色",
"flagNoLogo": "跳过 Logo",
"unknownCommand": "未知命令: {command}",
"unknownCommandHint": "运行 `nbtca --help` 查看可用命令。",
"unknownFlag": "未知选项: {flag}",
"unknownFlagHint": "运行 `nbtca --help` 查看可用选项。",
"invalidFlag": "选项 {flag} 对此命令无效。",
"invalidFlagHint": "运行 `nbtca --help` 查看命令用法。",
"invalidLang": "无效语言。请使用 `zh` 或 `en`。",
"invalidNext": "无效的 --next 值。请使用 --next=<数字>(>= 1)。",
"requiresTty": "交互模式需要 TTY 终端。",
"requiresTtyHint": "使用 `nbtca --help` 查看命令模式。"
}
}
86 changes: 48 additions & 38 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,9 @@ function validateFlags(command: string | undefined, flags: Set<string>): void {
return !KNOWN_FLAG_PREFIXES.some((prefix) => flag.startsWith(prefix));
});
if (unknown.length > 0) {
console.error(chalk.red(`Unknown flag: ${unknown[0]}`));
console.error(chalk.dim('Run `nbtca --help` to see available flags.'));
const trans0 = t();
console.error(chalk.red(fmt(trans0.cli.unknownFlag, { flag: unknown[0]! })));
console.error(chalk.dim(trans0.cli.unknownFlagHint));
process.exit(1);
}

Expand All @@ -142,44 +143,47 @@ function validateFlags(command: string | undefined, flags: Set<string>): void {
return !allowedPrefixes.some((prefix) => flag.startsWith(prefix));
});
if (disallowed.length > 0) {
console.error(chalk.red(`Flag ${disallowed[0]} is not valid for this command.`));
console.error(chalk.dim('Run `nbtca --help` to see command usage.'));
const trans1 = t();
console.error(chalk.red(fmt(trans1.cli.invalidFlag, { flag: disallowed[0]! })));
console.error(chalk.dim(trans1.cli.invalidFlagHint));
process.exit(1);
}
}

function printHelp(): void {
const trans = t();
const c = trans.cli;
console.log(chalk.bold('NBTCA Prompt'));
console.log();
console.log('Usage:');
console.log(' nbtca Interactive menu');
console.log(' nbtca <command> [flags] Run a command');
console.log(c.usage);
console.log(` nbtca ${c.interactive}`);
console.log(` nbtca <command> [flags] ${c.runCommand}`);
console.log();
console.log('Commands:');
console.log(' events Upcoming activities');
console.log(' docs Knowledge base');
console.log(' status Service health');
console.log(' website Official website URL');
console.log(' github GitHub organization URL');
console.log(' roadmap Project roadmap URL');
console.log(' repair Repair service URL');
console.log(' theme View or set theme');
console.log(' lang <zh|en> Set language');
console.log(' update Check for updates');
console.log(c.commands);
console.log(` events ${trans.menu.eventsDesc}`);
console.log(` docs ${trans.menu.docsDesc}`);
console.log(` status ${trans.menu.statusDesc}`);
console.log(` website ${c.cmdWebsite}`);
console.log(` github ${c.cmdGithub}`);
console.log(` roadmap ${c.cmdRoadmap}`);
console.log(` repair ${c.cmdRepair}`);
console.log(` theme ${c.cmdTheme}`);
console.log(` lang <zh|en> ${c.cmdLang}`);
console.log(` update ${c.cmdUpdate}`);
console.log();
console.log('Flags:');
console.log(' --version Show version');
console.log(' --help Show help');
console.log(' --open Open in browser (URL commands)');
console.log(' --json JSON output (events, status)');
console.log(' --today Today only (events)');
console.log(' --next=<n> Limit to next N (events)');
console.log(' --watch Live refresh (status)');
console.log(' --interval=<s> Refresh interval (status --watch)');
console.log(' --timeout=<ms> HTTP timeout (status)');
console.log(' --retries=<n> Retry count (status)');
console.log(' --plain No color');
console.log(' --no-logo Skip logo');
console.log(c.flags);
console.log(` --version ${c.flagVersion}`);
console.log(` --help ${c.flagHelp}`);
console.log(` --open ${c.flagOpen}`);
console.log(` --json ${c.flagJson}`);
console.log(` --today ${c.flagToday}`);
console.log(` --next=<n> ${c.flagNext}`);
console.log(` --watch ${c.flagWatch}`);
console.log(` --interval=<s> ${c.flagInterval}`);
console.log(` --timeout=<ms> ${c.flagTimeout}`);
console.log(` --retries=<n> ${c.flagRetries}`);
console.log(` --plain ${c.flagPlain}`);
console.log(` --no-logo ${c.flagNoLogo}`);
}

async function runEventsCommand(flags: Set<string>): Promise<void> {
Expand All @@ -199,7 +203,7 @@ async function runEventsCommand(flags: Set<string>): Promise<void> {
if (nextFlag) {
const n = Number.parseInt(nextFlag.split('=')[1] || '', 10);
if (!Number.isInteger(n) || n < 1) {
console.error(chalk.red('Invalid --next value. Use --next=<number> (>= 1).'));
console.error(chalk.red(t().cli.invalidNext));
process.exit(1);
}
events = events.slice(0, n);
Expand Down Expand Up @@ -339,8 +343,9 @@ async function runCommandMode(argv: string[]): Promise<void> {

if (!command) {
if (!hasInteractiveTerminal()) {
console.error(chalk.red('Interactive mode requires a TTY terminal.'));
console.error(chalk.dim('Use `nbtca --help` for command mode.'));
const cliTrans = t().cli;
console.error(chalk.red(cliTrans.requiresTty));
console.error(chalk.dim(cliTrans.requiresTtyHint));
process.exit(1);
}
await main({ skipLogo: flags.has('--no-logo') });
Expand All @@ -350,7 +355,7 @@ async function runCommandMode(argv: string[]): Promise<void> {
if (command === 'lang' || command === 'language') {
const language = (args[0] || '').toLowerCase() as Language;
if (language !== 'zh' && language !== 'en') {
console.error(chalk.red('Invalid language. Use `zh` or `en`.'));
console.error(chalk.red(t().cli.invalidLang));
process.exit(1);
}
const persisted = setLanguage(language);
Expand Down Expand Up @@ -381,8 +386,9 @@ async function runCommandMode(argv: string[]): Promise<void> {

const action = ACTION_ALIASES[command];
if (!action) {
console.error(chalk.red(`Unknown command: ${command}`));
console.error(chalk.dim('Run `nbtca --help` to see available commands.'));
const cliT = t().cli;
console.error(chalk.red(fmt(cliT.unknownCommand, { command })));
console.error(chalk.dim(cliT.unknownCommandHint));
process.exit(1);
}

Expand All @@ -393,7 +399,7 @@ async function runCommandMode(argv: string[]): Promise<void> {

if (action === 'status') {
const ok = await runStatusCommand(flags);
if (!ok) process.exit(1);
if (!ok && !flags.has('--json')) process.exit(1);
return;
}

Expand Down Expand Up @@ -421,9 +427,13 @@ async function runCommandMode(argv: string[]): Promise<void> {
const content = [
row(trans.about.project, APP_INFO.name),
row(trans.about.version, `v${APP_INFO.version}`),
row(trans.about.description, trans.about.descriptionText),
'',
link(trans.about.github, APP_INFO.repository),
link(trans.about.website, URLS.homepage),
link(trans.about.email, URLS.email),
'',
row(trans.about.license, `MIT | ${trans.about.author}: m1ngsama`),
].join('\n');
note(content, trans.about.title);
return;
Expand Down
Loading