@nbtca/prompt 1.0.24 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/data.js +0 -1
- package/dist/config/paths.js +22 -3
- package/dist/config/preferences.js +4 -8
- package/dist/core/text.js +14 -11
- package/dist/core/ui.js +16 -0
- package/dist/core/vim-keys.js +5 -2
- package/dist/features/calendar.js +18 -9
- package/dist/features/docs.js +169 -106
- package/dist/features/links.js +0 -5
- package/dist/features/settings.js +1 -1
- package/dist/features/status.js +9 -6
- package/dist/features/update.js +4 -4
- package/dist/i18n/index.js +15 -7
- package/dist/i18n/locales/en.json +39 -1
- package/dist/i18n/locales/zh.json +39 -1
- package/dist/index.js +61 -60
- package/dist/main.js +2 -12
- package/package.json +10 -11
package/dist/config/data.js
CHANGED
|
@@ -35,7 +35,6 @@ export const APP_INFO = {
|
|
|
35
35
|
name: 'Prompt',
|
|
36
36
|
version: readPackageVersion(),
|
|
37
37
|
description: '浙大宁波理工学院计算机协会',
|
|
38
|
-
fullDescription: 'NBTCA Prompt - 极简命令行工具',
|
|
39
38
|
author: 'm1ngsama <contact@m1ng.space>',
|
|
40
39
|
license: 'MIT',
|
|
41
40
|
repository: 'https://github.com/nbtca/prompt'
|
package/dist/config/paths.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
function getXdgConfigDir() {
|
|
5
|
+
const xdgHome = process.env['XDG_CONFIG_HOME'] || join(homedir(), '.config');
|
|
6
|
+
return join(xdgHome, 'nbtca');
|
|
7
|
+
}
|
|
8
|
+
function getLegacyConfigDir() {
|
|
9
|
+
return join(homedir(), '.nbtca');
|
|
10
|
+
}
|
|
2
11
|
export function getConfigDir() {
|
|
3
|
-
const
|
|
4
|
-
|
|
12
|
+
const xdgDir = getXdgConfigDir();
|
|
13
|
+
if (existsSync(xdgDir))
|
|
14
|
+
return xdgDir;
|
|
15
|
+
const legacyDir = getLegacyConfigDir();
|
|
16
|
+
if (existsSync(legacyDir))
|
|
17
|
+
return legacyDir;
|
|
18
|
+
return xdgDir;
|
|
19
|
+
}
|
|
20
|
+
export function getWritableConfigDir() {
|
|
21
|
+
const dir = getXdgConfigDir();
|
|
22
|
+
mkdirSync(dir, { recursive: true });
|
|
23
|
+
return dir;
|
|
5
24
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { getConfigDir } from './paths.js';
|
|
3
|
+
import { getConfigDir, getWritableConfigDir } from './paths.js';
|
|
4
4
|
const DEFAULT_PREFERENCES = {
|
|
5
5
|
iconMode: 'auto',
|
|
6
6
|
colorMode: 'auto',
|
|
@@ -8,11 +8,8 @@ const DEFAULT_PREFERENCES = {
|
|
|
8
8
|
function getPreferencesPath() {
|
|
9
9
|
return path.join(getConfigDir(), 'preferences.json');
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
if (!fs.existsSync(configDir)) {
|
|
14
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
15
|
-
}
|
|
11
|
+
function getWritablePreferencesPath() {
|
|
12
|
+
return path.join(getWritableConfigDir(), 'preferences.json');
|
|
16
13
|
}
|
|
17
14
|
export function loadPreferences() {
|
|
18
15
|
try {
|
|
@@ -32,8 +29,7 @@ export function loadPreferences() {
|
|
|
32
29
|
}
|
|
33
30
|
function savePreferences(preferences) {
|
|
34
31
|
try {
|
|
35
|
-
|
|
36
|
-
fs.writeFileSync(getPreferencesPath(), JSON.stringify(preferences, null, 2));
|
|
32
|
+
fs.writeFileSync(getWritablePreferencesPath(), JSON.stringify(preferences, null, 2));
|
|
37
33
|
return true;
|
|
38
34
|
}
|
|
39
35
|
catch {
|
package/dist/core/text.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
/** Width of a single Unicode character: 2 for CJK/fullwidth, 1 otherwise. */
|
|
2
2
|
function charWidth(ch) {
|
|
3
3
|
const cp = ch.codePointAt(0) ?? 0;
|
|
4
|
-
return ((cp >= 0x1100 && cp <= 0x115F) ||
|
|
5
|
-
(cp >= 0x2E80 && cp <= 0x303F) ||
|
|
6
|
-
(cp >= 0x3040 && cp <= 0x33FF) ||
|
|
7
|
-
(cp >= 0x3400 && cp <= 0x4DBF) ||
|
|
8
|
-
(cp >= 0x4E00 && cp <= 0x9FFF) ||
|
|
9
|
-
(cp >= 0xAC00 && cp <= 0xD7AF) ||
|
|
10
|
-
(cp >= 0xF900 && cp <= 0xFAFF) ||
|
|
11
|
-
(cp >= 0xFE30 && cp <= 0xFE4F) ||
|
|
12
|
-
(cp >= 0xFF00 && cp <= 0xFF60) ||
|
|
13
|
-
(cp >= 0xFFE0 && cp <= 0xFFE6)
|
|
14
|
-
|
|
4
|
+
return ((cp >= 0x1100 && cp <= 0x115F) ||
|
|
5
|
+
(cp >= 0x2E80 && cp <= 0x303F) ||
|
|
6
|
+
(cp >= 0x3040 && cp <= 0x33FF) ||
|
|
7
|
+
(cp >= 0x3400 && cp <= 0x4DBF) ||
|
|
8
|
+
(cp >= 0x4E00 && cp <= 0x9FFF) ||
|
|
9
|
+
(cp >= 0xAC00 && cp <= 0xD7AF) ||
|
|
10
|
+
(cp >= 0xF900 && cp <= 0xFAFF) ||
|
|
11
|
+
(cp >= 0xFE30 && cp <= 0xFE4F) ||
|
|
12
|
+
(cp >= 0xFF00 && cp <= 0xFF60) ||
|
|
13
|
+
(cp >= 0xFFE0 && cp <= 0xFFE6) ||
|
|
14
|
+
(cp >= 0x20000 && cp <= 0x2A6DF) ||
|
|
15
|
+
(cp >= 0x2A700 && cp <= 0x2CEAF) ||
|
|
16
|
+
(cp >= 0x2CEB0 && cp <= 0x2EBEF) ||
|
|
17
|
+
(cp >= 0x30000 && cp <= 0x323AF)) ? 2 : 1;
|
|
15
18
|
}
|
|
16
19
|
/** Strip ANSI escape sequences from a string. */
|
|
17
20
|
// eslint-disable-next-line no-control-regex
|
package/dist/core/ui.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { log, spinner as clackSpinner } from '@clack/prompts';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import { pickIcon } from './icons.js';
|
|
8
|
+
import { t } from '../i18n/index.js';
|
|
8
9
|
/**
|
|
9
10
|
* Display success message
|
|
10
11
|
*/
|
|
@@ -62,3 +63,18 @@ export function createSpinner(msg) {
|
|
|
62
63
|
s.start(msg);
|
|
63
64
|
return s;
|
|
64
65
|
}
|
|
66
|
+
export function handleGracefulExit(err) {
|
|
67
|
+
const message = err instanceof Error ? err.message : String(err ?? '');
|
|
68
|
+
if (message.includes('SIGINT') || message.includes('User force closed')) {
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(chalk.dim(t().common.goodbye));
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
if (message) {
|
|
74
|
+
console.error(message);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
console.error('Error occurred:', err);
|
|
78
|
+
}
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
package/dist/core/vim-keys.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
// Maps single-byte vim keys to terminal escape sequences (ranger-style hjkl)
|
|
8
8
|
const VIM_TO_SEQ = {
|
|
9
|
-
h: Buffer.from('\u0003'), // back/cancel (ranger: go to parent)
|
|
10
9
|
j: Buffer.from('\u001b[B'), // down arrow
|
|
11
10
|
k: Buffer.from('\u001b[A'), // up arrow
|
|
12
11
|
l: Buffer.from('\r'), // enter/confirm (ranger: open/enter)
|
|
@@ -14,13 +13,17 @@ const VIM_TO_SEQ = {
|
|
|
14
13
|
G: Buffer.from('\u001b[F'), // end (last item)
|
|
15
14
|
q: Buffer.from('\u0003'), // quit
|
|
16
15
|
};
|
|
16
|
+
let vimActive = true;
|
|
17
|
+
export function setVimKeysActive(active) {
|
|
18
|
+
vimActive = active;
|
|
19
|
+
}
|
|
17
20
|
export function enableVimKeys() {
|
|
18
21
|
const stdin = process.stdin;
|
|
19
22
|
if (!stdin.isTTY)
|
|
20
23
|
return;
|
|
21
24
|
const originalEmit = stdin.emit.bind(stdin);
|
|
22
25
|
stdin.emit = function (event, ...args) {
|
|
23
|
-
if (event === 'data') {
|
|
26
|
+
if (event === 'data' && vimActive) {
|
|
24
27
|
const chunk = args[0];
|
|
25
28
|
if (Buffer.isBuffer(chunk) && chunk.length === 1) {
|
|
26
29
|
const seq = VIM_TO_SEQ[String.fromCharCode(chunk[0])];
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* ICS calendar module
|
|
3
3
|
* Fetches and renders upcoming events with Unicode box table.
|
|
4
4
|
*/
|
|
5
|
-
import axios from 'axios';
|
|
6
5
|
import ICAL from 'ical.js';
|
|
7
6
|
import chalk from 'chalk';
|
|
8
7
|
import { select, isCancel } from '@clack/prompts';
|
|
@@ -13,13 +12,17 @@ import { t } from '../i18n/index.js';
|
|
|
13
12
|
import { APP_INFO, URLS } from '../config/data.js';
|
|
14
13
|
export async function fetchEvents() {
|
|
15
14
|
try {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
17
|
+
const response = await fetch('https://ical.nbtca.space', {
|
|
18
|
+
signal: controller.signal,
|
|
19
|
+
headers: { 'User-Agent': `NBTCA-CLI/${APP_INFO.version}` },
|
|
21
20
|
});
|
|
22
|
-
|
|
21
|
+
clearTimeout(timeout);
|
|
22
|
+
if (!response.ok)
|
|
23
|
+
throw new Error(`HTTP ${response.status}`);
|
|
24
|
+
const data = await response.text();
|
|
25
|
+
const jcalData = ICAL.parse(data);
|
|
23
26
|
const comp = new ICAL.Component(jcalData);
|
|
24
27
|
const vevents = comp.getAllSubcomponents('vevent');
|
|
25
28
|
const events = [];
|
|
@@ -46,7 +49,9 @@ export async function fetchEvents() {
|
|
|
46
49
|
return events;
|
|
47
50
|
}
|
|
48
51
|
catch (err) {
|
|
49
|
-
const detail = err instanceof Error
|
|
52
|
+
const detail = err instanceof Error
|
|
53
|
+
? (err.name === 'AbortError' ? 'Request timed out' : err.message)
|
|
54
|
+
: String(err);
|
|
50
55
|
throw new Error(`${t().calendar.error}: ${detail}`);
|
|
51
56
|
}
|
|
52
57
|
}
|
|
@@ -61,8 +66,12 @@ export function serializeEvents(events) {
|
|
|
61
66
|
}));
|
|
62
67
|
}
|
|
63
68
|
function formatDate(date) {
|
|
69
|
+
const now = new Date();
|
|
64
70
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
65
71
|
const day = String(date.getDate()).padStart(2, '0');
|
|
72
|
+
if (date.getFullYear() !== now.getFullYear()) {
|
|
73
|
+
return `${date.getFullYear()}-${month}-${day}`;
|
|
74
|
+
}
|
|
66
75
|
return `${month}-${day}`;
|
|
67
76
|
}
|
|
68
77
|
function formatTime(date) {
|
|
@@ -78,7 +87,7 @@ export function renderEventsTable(events, options) {
|
|
|
78
87
|
const color = options?.color !== false;
|
|
79
88
|
if (events.length === 0)
|
|
80
89
|
return trans.calendar.noEvents;
|
|
81
|
-
const dateWidth =
|
|
90
|
+
const dateWidth = 16;
|
|
82
91
|
const titleWidth = 30;
|
|
83
92
|
const locationWidth = 16;
|
|
84
93
|
const h = pickIcon('─', '-');
|
package/dist/features/docs.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* 知识库终端查看模块
|
|
3
3
|
* 获取并渲染Markdown文档
|
|
4
4
|
*/
|
|
5
|
-
import axios from 'axios';
|
|
6
5
|
import { marked } from 'marked';
|
|
7
6
|
import TerminalRenderer from 'marked-terminal';
|
|
8
7
|
import chalk from 'chalk';
|
|
@@ -12,7 +11,8 @@ import { error, warning, success, createSpinner } from '../core/ui.js';
|
|
|
12
11
|
import { pickIcon } from '../core/icons.js';
|
|
13
12
|
import { spawn, execFileSync } from 'child_process';
|
|
14
13
|
import { APP_INFO, GITHUB_REPO, URLS } from '../config/data.js';
|
|
15
|
-
import { t } from '../i18n/index.js';
|
|
14
|
+
import { t, fmt } from '../i18n/index.js';
|
|
15
|
+
import { setVimKeysActive } from '../core/vim-keys.js';
|
|
16
16
|
function detectTerminalType() {
|
|
17
17
|
const term = (process.env['TERM'] || '').toLowerCase();
|
|
18
18
|
const termProgram = (process.env['TERM_PROGRAM'] || '').toLowerCase();
|
|
@@ -31,7 +31,8 @@ function detectTerminalType() {
|
|
|
31
31
|
/** Check whether an external command exists on PATH (once at startup). */
|
|
32
32
|
function commandExists(cmd) {
|
|
33
33
|
try {
|
|
34
|
-
|
|
34
|
+
const check = process.platform === 'win32' ? 'where' : 'which';
|
|
35
|
+
execFileSync(check, [cmd], { stdio: 'ignore' });
|
|
35
36
|
return true;
|
|
36
37
|
}
|
|
37
38
|
catch {
|
|
@@ -55,8 +56,9 @@ function ensureMarkedConfigured() {
|
|
|
55
56
|
if (_markedConfigured)
|
|
56
57
|
return;
|
|
57
58
|
_markedConfigured = true;
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
marked.use({
|
|
60
|
+
renderer: new TerminalRenderer(getRendererOptions(getTerminalType()))
|
|
61
|
+
});
|
|
60
62
|
}
|
|
61
63
|
// ─── marked-terminal renderer ─────────────────────────────────────────────────
|
|
62
64
|
function getRendererOptions(type) {
|
|
@@ -135,6 +137,18 @@ function getAnyCacheValue(cache, key) {
|
|
|
135
137
|
function setCacheValue(cache, key, value, ttlMs) {
|
|
136
138
|
cache.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
137
139
|
}
|
|
140
|
+
const DIR_CACHE_MAX = 30;
|
|
141
|
+
const FILE_CACHE_MAX = 50;
|
|
142
|
+
const RENDER_CACHE_MAX = 50;
|
|
143
|
+
function evictStalest(cache, maxSize) {
|
|
144
|
+
if (cache.size <= maxSize)
|
|
145
|
+
return;
|
|
146
|
+
const oldest = [...cache.entries()]
|
|
147
|
+
.sort((a, b) => a[1].expiresAt - b[1].expiresAt)
|
|
148
|
+
.slice(0, cache.size - maxSize);
|
|
149
|
+
for (const [key] of oldest)
|
|
150
|
+
cache.delete(key);
|
|
151
|
+
}
|
|
138
152
|
function contentFingerprint(content) {
|
|
139
153
|
const head = content.slice(0, 80);
|
|
140
154
|
const tail = content.slice(-80);
|
|
@@ -157,12 +171,29 @@ async function fetchGitHubDirectory(path = '', options = {}) {
|
|
|
157
171
|
try {
|
|
158
172
|
const headers = {
|
|
159
173
|
'Accept': 'application/vnd.github.v3+json',
|
|
160
|
-
'User-Agent': `NBTCA-CLI/${APP_INFO.version}
|
|
174
|
+
'User-Agent': `NBTCA-CLI/${APP_INFO.version}`,
|
|
161
175
|
};
|
|
162
176
|
if (GITHUB_TOKEN)
|
|
163
177
|
headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
|
|
164
|
-
const
|
|
165
|
-
const
|
|
178
|
+
const controller = new AbortController();
|
|
179
|
+
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
180
|
+
const response = await fetch(url, { signal: controller.signal, headers });
|
|
181
|
+
clearTimeout(timeout);
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
const trans = t();
|
|
184
|
+
if (response.status === 403) {
|
|
185
|
+
const rateLimitRemaining = response.headers.get('x-ratelimit-remaining');
|
|
186
|
+
const rateLimitReset = response.headers.get('x-ratelimit-reset');
|
|
187
|
+
if (rateLimitRemaining === '0' && rateLimitReset) {
|
|
188
|
+
const resetDate = new Date(Number.parseInt(rateLimitReset, 10) * 1000);
|
|
189
|
+
throw new Error(`${fmt(trans.docs.githubRateLimited, { time: resetDate.toLocaleTimeString() })}\n${trans.docs.githubTokenHint}`);
|
|
190
|
+
}
|
|
191
|
+
throw new Error(`${trans.docs.githubForbidden}\n${trans.docs.githubTokenHint}`);
|
|
192
|
+
}
|
|
193
|
+
throw new Error(`HTTP ${response.status}`);
|
|
194
|
+
}
|
|
195
|
+
const data = (await response.json());
|
|
196
|
+
const items = data
|
|
166
197
|
.filter((item) => !item.name.startsWith('.') &&
|
|
167
198
|
!SKIP_NAMES.has(item.name) &&
|
|
168
199
|
!(item.type === 'file' && !item.name.endsWith('.md')))
|
|
@@ -180,6 +211,7 @@ async function fetchGitHubDirectory(path = '', options = {}) {
|
|
|
180
211
|
return a.name.localeCompare(b.name);
|
|
181
212
|
});
|
|
182
213
|
setCacheValue(dirCache, cacheKey, items, DIR_CACHE_TTL_MS);
|
|
214
|
+
evictStalest(dirCache, DIR_CACHE_MAX);
|
|
183
215
|
return { data: items, fromCache: false, staleFallback: false };
|
|
184
216
|
}
|
|
185
217
|
catch (err) {
|
|
@@ -188,18 +220,10 @@ async function fetchGitHubDirectory(path = '', options = {}) {
|
|
|
188
220
|
return { data: staleCached, fromCache: true, staleFallback: true };
|
|
189
221
|
}
|
|
190
222
|
const trans = t();
|
|
191
|
-
const errorMessage = err instanceof Error
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const rateLimitReset = axiosErr.response.headers?.['x-ratelimit-reset'];
|
|
196
|
-
if (rateLimitRemaining === '0' && rateLimitReset) {
|
|
197
|
-
const resetDate = new Date(Number.parseInt(rateLimitReset, 10) * 1000);
|
|
198
|
-
throw new Error(`${trans.docs.githubRateLimited.replace('{time}', resetDate.toLocaleTimeString())}\n${trans.docs.githubTokenHint}`);
|
|
199
|
-
}
|
|
200
|
-
throw new Error(`${trans.docs.githubForbidden}\n${trans.docs.githubTokenHint}`);
|
|
201
|
-
}
|
|
202
|
-
throw new Error(trans.docs.fetchDirFailed.replace('{error}', errorMessage));
|
|
223
|
+
const errorMessage = err instanceof Error
|
|
224
|
+
? (err.name === 'AbortError' ? 'Request timed out' : err.message)
|
|
225
|
+
: String(err);
|
|
226
|
+
throw new Error(fmt(trans.docs.fetchDirFailed, { error: errorMessage }));
|
|
203
227
|
}
|
|
204
228
|
}
|
|
205
229
|
async function fetchGitHubRawContent(path, options = {}) {
|
|
@@ -211,9 +235,18 @@ async function fetchGitHubRawContent(path, options = {}) {
|
|
|
211
235
|
}
|
|
212
236
|
const url = `https://raw.githubusercontent.com/${GITHUB_REPO.owner}/${GITHUB_REPO.repo}/${GITHUB_REPO.branch}/${path}`;
|
|
213
237
|
try {
|
|
214
|
-
const
|
|
215
|
-
const
|
|
238
|
+
const controller = new AbortController();
|
|
239
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
240
|
+
const response = await fetch(url, {
|
|
241
|
+
signal: controller.signal,
|
|
242
|
+
headers: { 'User-Agent': `NBTCA-CLI/${APP_INFO.version}` },
|
|
243
|
+
});
|
|
244
|
+
clearTimeout(timeout);
|
|
245
|
+
if (!response.ok)
|
|
246
|
+
throw new Error(`HTTP ${response.status}`);
|
|
247
|
+
const content = await response.text();
|
|
216
248
|
setCacheValue(fileCache, path, content, FILE_CACHE_TTL_MS);
|
|
249
|
+
evictStalest(fileCache, FILE_CACHE_MAX);
|
|
217
250
|
return { data: content, fromCache: false, staleFallback: false };
|
|
218
251
|
}
|
|
219
252
|
catch (err) {
|
|
@@ -222,8 +255,10 @@ async function fetchGitHubRawContent(path, options = {}) {
|
|
|
222
255
|
return { data: staleCached, fromCache: true, staleFallback: true };
|
|
223
256
|
}
|
|
224
257
|
const trans = t();
|
|
225
|
-
const errorMessage = err instanceof Error
|
|
226
|
-
|
|
258
|
+
const errorMessage = err instanceof Error
|
|
259
|
+
? (err.name === 'AbortError' ? 'Request timed out' : err.message)
|
|
260
|
+
: String(err);
|
|
261
|
+
throw new Error(fmt(trans.docs.fetchFileFailed, { error: errorMessage }));
|
|
227
262
|
}
|
|
228
263
|
}
|
|
229
264
|
// ─── Content cleaning ─────────────────────────────────────────────────────────
|
|
@@ -361,22 +396,41 @@ async function displayWithLess(rendered, title, filePath, readTime) {
|
|
|
361
396
|
});
|
|
362
397
|
}
|
|
363
398
|
// ─── Directory browser ────────────────────────────────────────────────────────
|
|
364
|
-
async function browseDirectory(
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
399
|
+
async function browseDirectory(initialPath = '') {
|
|
400
|
+
let currentPath = initialPath;
|
|
401
|
+
while (true) {
|
|
402
|
+
const trans = t();
|
|
403
|
+
let items;
|
|
404
|
+
try {
|
|
405
|
+
const s = createSpinner(currentPath ? `${trans.docs.loadingDir}: ${currentPath}` : trans.docs.loading);
|
|
406
|
+
const result = await fetchGitHubDirectory(currentPath);
|
|
407
|
+
items = result.data;
|
|
408
|
+
s.stop(currentPath || trans.docs.chooseDoc);
|
|
409
|
+
if (result.staleFallback) {
|
|
410
|
+
warning(trans.docs.usingCachedData);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
error(trans.docs.loadError);
|
|
415
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
416
|
+
console.log(chalk.gray(` ${trans.docs.errorHint}: ${errMsg}`));
|
|
417
|
+
setVimKeysActive(false);
|
|
418
|
+
const retry = await confirm({ message: trans.docs.retry });
|
|
419
|
+
setVimKeysActive(true);
|
|
420
|
+
if (!isCancel(retry) && retry)
|
|
421
|
+
continue;
|
|
422
|
+
return;
|
|
373
423
|
}
|
|
374
424
|
if (items.length === 0) {
|
|
375
425
|
warning(trans.docs.emptyDir);
|
|
426
|
+
if (currentPath) {
|
|
427
|
+
currentPath = currentPath.split('/').slice(0, -1).join('/');
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
376
430
|
return;
|
|
377
431
|
}
|
|
378
432
|
const options = [
|
|
379
|
-
...(
|
|
433
|
+
...(currentPath ? [{ value: '__back__', label: chalk.dim(trans.docs.upToParent) }] : []),
|
|
380
434
|
...items.map(item => ({
|
|
381
435
|
value: item.path,
|
|
382
436
|
label: item.type === 'dir'
|
|
@@ -387,93 +441,89 @@ async function browseDirectory(dirPath = '') {
|
|
|
387
441
|
{ value: '__exit__', label: chalk.dim(trans.docs.returnToMenu) },
|
|
388
442
|
];
|
|
389
443
|
const selected = await select({
|
|
390
|
-
message:
|
|
444
|
+
message: currentPath ? `${trans.docs.currentDir}: ${currentPath}` : trans.docs.chooseDoc,
|
|
391
445
|
options,
|
|
392
446
|
});
|
|
393
447
|
if (isCancel(selected) || selected === '__exit__')
|
|
394
448
|
return;
|
|
395
449
|
if (selected === '__back__') {
|
|
396
|
-
|
|
397
|
-
|
|
450
|
+
currentPath = currentPath.split('/').slice(0, -1).join('/');
|
|
451
|
+
continue;
|
|
398
452
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
else if (item?.type === 'file') {
|
|
405
|
-
await viewMarkdownFile(selected);
|
|
406
|
-
await browseDirectory(dirPath);
|
|
407
|
-
}
|
|
453
|
+
const item = items.find(i => i.path === selected);
|
|
454
|
+
if (item?.type === 'dir') {
|
|
455
|
+
currentPath = selected;
|
|
456
|
+
continue;
|
|
408
457
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
error(trans.docs.loadError);
|
|
412
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
413
|
-
console.log(chalk.gray(` ${trans.docs.errorHint}: ${errMsg}`));
|
|
414
|
-
const retry = await confirm({ message: trans.docs.retry });
|
|
415
|
-
if (!isCancel(retry) && retry) {
|
|
416
|
-
await browseDirectory(dirPath);
|
|
458
|
+
if (item?.type === 'file') {
|
|
459
|
+
await viewMarkdownFile(selected);
|
|
417
460
|
}
|
|
418
461
|
}
|
|
419
462
|
}
|
|
420
463
|
// ─── Document viewer ──────────────────────────────────────────────────────────
|
|
421
464
|
async function viewMarkdownFile(filePath) {
|
|
422
465
|
const trans = t();
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
466
|
+
while (true) {
|
|
467
|
+
try {
|
|
468
|
+
ensureMarkedConfigured();
|
|
469
|
+
const s = createSpinner(`${trans.docs.loadingFile}: ${filePath}`);
|
|
470
|
+
const rawResult = await fetchGitHubRawContent(filePath);
|
|
471
|
+
if (rawResult.staleFallback) {
|
|
472
|
+
warning(trans.docs.usingCachedData);
|
|
473
|
+
}
|
|
474
|
+
const rawContent = rawResult.data;
|
|
475
|
+
const fingerprint = contentFingerprint(rawContent);
|
|
476
|
+
const cachedRendered = getFreshCacheValue(renderCache, filePath);
|
|
477
|
+
let renderedDoc;
|
|
478
|
+
if (cachedRendered && cachedRendered.fingerprint === fingerprint) {
|
|
479
|
+
renderedDoc = cachedRendered;
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
const cleaned = cleanMarkdownContent(rawContent, getTerminalType());
|
|
483
|
+
const title = extractDocTitle(rawContent, cleaned) || filePath.split('/').pop() || filePath;
|
|
484
|
+
const readTime = estimateReadTime(cleaned);
|
|
485
|
+
const rendered = await marked(cleaned);
|
|
486
|
+
renderedDoc = { fingerprint, cleaned, rendered, title, readTime };
|
|
487
|
+
setCacheValue(renderCache, filePath, renderedDoc, RENDER_CACHE_TTL_MS);
|
|
488
|
+
evictStalest(renderCache, RENDER_CACHE_MAX);
|
|
489
|
+
}
|
|
490
|
+
s.stop(`${chalk.bold(renderedDoc.title)} ${chalk.dim(renderedDoc.readTime)}`);
|
|
491
|
+
if (hasGlow()) {
|
|
492
|
+
await displayWithGlow(renderedDoc.cleaned);
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
await displayWithLess(renderedDoc.rendered, renderedDoc.title, filePath, renderedDoc.readTime);
|
|
496
|
+
}
|
|
497
|
+
console.log();
|
|
498
|
+
success(trans.docs.docCompleted);
|
|
499
|
+
console.log();
|
|
500
|
+
const action = await select({
|
|
501
|
+
message: trans.docs.chooseAction,
|
|
502
|
+
options: [
|
|
503
|
+
{ value: 'back', label: trans.docs.backToList },
|
|
504
|
+
{ value: 'reread', label: trans.docs.reread },
|
|
505
|
+
{ value: 'browser', label: trans.docs.openBrowser },
|
|
506
|
+
],
|
|
507
|
+
});
|
|
508
|
+
if (isCancel(action) || action === 'back')
|
|
509
|
+
return;
|
|
510
|
+
if (action === 'browser') {
|
|
511
|
+
await openDocsInBrowser(filePath);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
// action === 'reread' → continue loop
|
|
451
515
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
});
|
|
463
|
-
if (isCancel(action))
|
|
516
|
+
catch (err) {
|
|
517
|
+
error(trans.docs.loadError);
|
|
518
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
519
|
+
console.log(chalk.gray(` ${trans.docs.errorHint}: ${errMsg}`));
|
|
520
|
+
setVimKeysActive(false);
|
|
521
|
+
const openBrowser = await confirm({ message: trans.docs.openBrowserPrompt });
|
|
522
|
+
setVimKeysActive(true);
|
|
523
|
+
if (!isCancel(openBrowser) && openBrowser) {
|
|
524
|
+
await openDocsInBrowser(filePath);
|
|
525
|
+
}
|
|
464
526
|
return;
|
|
465
|
-
if (action === 'browser')
|
|
466
|
-
await openDocsInBrowser(filePath);
|
|
467
|
-
if (action === 'reread')
|
|
468
|
-
await viewMarkdownFile(filePath);
|
|
469
|
-
}
|
|
470
|
-
catch (err) {
|
|
471
|
-
error(trans.docs.loadError);
|
|
472
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
473
|
-
console.log(chalk.gray(` ${trans.docs.errorHint}: ${errMsg}`));
|
|
474
|
-
const openBrowser = await confirm({ message: trans.docs.openBrowserPrompt });
|
|
475
|
-
if (!isCancel(openBrowser) && openBrowser) {
|
|
476
|
-
await openDocsInBrowser(filePath);
|
|
477
527
|
}
|
|
478
528
|
}
|
|
479
529
|
}
|
|
@@ -497,10 +547,12 @@ export async function openDocsInBrowser(path) {
|
|
|
497
547
|
// ─── Search ────────────────────────────────────────────────────────────────────
|
|
498
548
|
async function searchDocs() {
|
|
499
549
|
const trans = t();
|
|
550
|
+
setVimKeysActive(false);
|
|
500
551
|
const query = await text({
|
|
501
552
|
message: trans.docs.searchPrompt,
|
|
502
553
|
placeholder: trans.docs.searchPlaceholder,
|
|
503
554
|
});
|
|
555
|
+
setVimKeysActive(true);
|
|
504
556
|
if (isCancel(query) || !query.trim())
|
|
505
557
|
return;
|
|
506
558
|
const keyword = query.trim().toLowerCase();
|
|
@@ -523,6 +575,17 @@ async function searchDocs() {
|
|
|
523
575
|
}
|
|
524
576
|
}
|
|
525
577
|
}
|
|
578
|
+
// Also search already-cached file content
|
|
579
|
+
for (const [cachedPath, entry] of fileCache) {
|
|
580
|
+
if (results.some(r => r.path === cachedPath))
|
|
581
|
+
continue;
|
|
582
|
+
if (entry.value.toLowerCase().includes(keyword)) {
|
|
583
|
+
const name = cachedPath.split('/').pop() || cachedPath;
|
|
584
|
+
const parentDir = cachedPath.split('/').slice(0, -1).join('/');
|
|
585
|
+
const matchedCat = categories.find(c => parentDir.startsWith(c.path));
|
|
586
|
+
results.push({ name, path: cachedPath, category: matchedCat?.name ?? parentDir });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
526
589
|
s.stop(`${results.length} ${trans.docs.searchResults}`);
|
|
527
590
|
}
|
|
528
591
|
catch {
|
package/dist/features/links.js
CHANGED
|
@@ -35,8 +35,3 @@ export async function showLinksMenu() {
|
|
|
35
35
|
return;
|
|
36
36
|
await openUrl(selected);
|
|
37
37
|
}
|
|
38
|
-
/** Direct openers for CLI non-interactive mode */
|
|
39
|
-
export async function openHomepage() { await openUrl(URLS.homepage); }
|
|
40
|
-
export async function openGithub() { await openUrl(URLS.github); }
|
|
41
|
-
export async function openRoadmap() { await openUrl(URLS.roadmap); }
|
|
42
|
-
export async function openRepairService() { await openUrl(URLS.repair); }
|
|
@@ -26,7 +26,7 @@ function showAbout() {
|
|
|
26
26
|
const content = [
|
|
27
27
|
row(trans.about.project, APP_INFO.name),
|
|
28
28
|
row(trans.about.version, `v${APP_INFO.version}`),
|
|
29
|
-
row(trans.about.description,
|
|
29
|
+
row(trans.about.description, trans.about.descriptionText),
|
|
30
30
|
'',
|
|
31
31
|
link(trans.about.github, APP_INFO.repository),
|
|
32
32
|
link(trans.about.website, URLS.homepage),
|
package/dist/features/status.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
1
|
import chalk from 'chalk';
|
|
3
2
|
import { APP_INFO, URLS } from '../config/data.js';
|
|
4
3
|
import { pickIcon } from '../core/icons.js';
|
|
@@ -18,19 +17,23 @@ function getServiceTargets() {
|
|
|
18
17
|
async function checkService(name, url, timeoutMs) {
|
|
19
18
|
const start = Date.now();
|
|
20
19
|
try {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
22
|
+
const response = await fetch(url, {
|
|
23
|
+
signal: controller.signal,
|
|
24
|
+
redirect: 'follow',
|
|
25
25
|
headers: { 'User-Agent': `NBTCA-CLI/${APP_INFO.version}` },
|
|
26
26
|
});
|
|
27
|
+
clearTimeout(timeout);
|
|
27
28
|
const latencyMs = Date.now() - start;
|
|
28
29
|
const ok = response.status >= 200 && response.status < 400;
|
|
29
30
|
return { name, url, ok, statusCode: response.status, latencyMs };
|
|
30
31
|
}
|
|
31
32
|
catch (err) {
|
|
32
33
|
const latencyMs = Date.now() - start;
|
|
33
|
-
const error = err instanceof Error
|
|
34
|
+
const error = err instanceof Error
|
|
35
|
+
? (err.name === 'AbortError' ? 'Request timed out' : err.message)
|
|
36
|
+
: String(err);
|
|
34
37
|
return { name, url, ok: false, latencyMs, error };
|
|
35
38
|
}
|
|
36
39
|
}
|
package/dist/features/update.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { APP_INFO } from '../config/data.js';
|
|
7
|
-
import { t } from '../i18n/index.js';
|
|
7
|
+
import { t, fmt } from '../i18n/index.js';
|
|
8
8
|
const NPM_REGISTRY_URL = `https://registry.npmjs.org/@nbtca/prompt/latest`;
|
|
9
9
|
/**
|
|
10
10
|
* Fetch latest version from npm registry.
|
|
@@ -52,7 +52,7 @@ export async function checkForUpdate() {
|
|
|
52
52
|
if (!latest || !isNewer(APP_INFO.version, latest))
|
|
53
53
|
return null;
|
|
54
54
|
const trans = t();
|
|
55
|
-
return `${trans.update.available
|
|
55
|
+
return `${fmt(trans.update.available, { latest, current: APP_INFO.version })} ${chalk.dim(trans.update.command)}`;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* Explicit update check command (nbtca update).
|
|
@@ -65,10 +65,10 @@ export async function runUpdateCheck() {
|
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
67
|
if (isNewer(APP_INFO.version, latest)) {
|
|
68
|
-
console.log(chalk.yellow(`${trans.update.available
|
|
68
|
+
console.log(chalk.yellow(`${fmt(trans.update.available, { latest, current: APP_INFO.version })}`));
|
|
69
69
|
console.log(chalk.dim(trans.update.command));
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
|
-
console.log(chalk.green(`${trans.update.upToDate
|
|
72
|
+
console.log(chalk.green(`${fmt(trans.update.upToDate, { version: APP_INFO.version })}`));
|
|
73
73
|
}
|
|
74
74
|
}
|
package/dist/i18n/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import fs from 'fs';
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { dirname } from 'path';
|
|
9
|
-
import { getConfigDir } from '../config/paths.js';
|
|
9
|
+
import { getConfigDir, getWritableConfigDir } from '../config/paths.js';
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
11
|
const __dirname = dirname(__filename);
|
|
12
12
|
/**
|
|
@@ -14,11 +14,17 @@ const __dirname = dirname(__filename);
|
|
|
14
14
|
*/
|
|
15
15
|
let currentLanguage = 'zh'; // Default to Chinese
|
|
16
16
|
/**
|
|
17
|
-
* Get language configuration file path
|
|
17
|
+
* Get language configuration file path (read, with legacy fallback)
|
|
18
18
|
*/
|
|
19
19
|
function getLanguageConfigPath() {
|
|
20
20
|
return path.join(getConfigDir(), 'language.json');
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Get writable language configuration file path (XDG, creates dir)
|
|
24
|
+
*/
|
|
25
|
+
function getWritableLanguageConfigPath() {
|
|
26
|
+
return path.join(getWritableConfigDir(), 'language.json');
|
|
27
|
+
}
|
|
22
28
|
/**
|
|
23
29
|
* Load language preference from config file
|
|
24
30
|
*/
|
|
@@ -40,11 +46,7 @@ export function loadLanguagePreference() {
|
|
|
40
46
|
*/
|
|
41
47
|
export function saveLanguagePreference(language) {
|
|
42
48
|
try {
|
|
43
|
-
const
|
|
44
|
-
if (!fs.existsSync(configDir)) {
|
|
45
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
46
|
-
}
|
|
47
|
-
const configPath = getLanguageConfigPath();
|
|
49
|
+
const configPath = getWritableLanguageConfigPath();
|
|
48
50
|
fs.writeFileSync(configPath, JSON.stringify({ language }, null, 2));
|
|
49
51
|
currentLanguage = language;
|
|
50
52
|
return true;
|
|
@@ -95,6 +97,12 @@ export function t() {
|
|
|
95
97
|
}
|
|
96
98
|
return translationsCache.get(currentLanguage);
|
|
97
99
|
}
|
|
100
|
+
export function fmt(template, vars) {
|
|
101
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
102
|
+
const val = vars[key];
|
|
103
|
+
return val !== undefined ? String(val) : `{${key}}`;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
98
106
|
/**
|
|
99
107
|
* Clear translation cache (useful when switching languages)
|
|
100
108
|
*/
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"project": "Project",
|
|
29
29
|
"version": "Version",
|
|
30
30
|
"description": "Description",
|
|
31
|
+
"descriptionText": "NBTCA Prompt - Minimalist CLI Tool",
|
|
31
32
|
"github": "GitHub",
|
|
32
33
|
"website": "Website",
|
|
33
34
|
"email": "Email",
|
|
@@ -91,7 +92,8 @@
|
|
|
91
92
|
"searchPlaceholder": "Enter keyword...",
|
|
92
93
|
"searching": "Searching documents...",
|
|
93
94
|
"searchResults": "results found",
|
|
94
|
-
"searchNoResults": "No documents match your search"
|
|
95
|
+
"searchNoResults": "No documents match your search",
|
|
96
|
+
"loadingFile": "Loading"
|
|
95
97
|
},
|
|
96
98
|
"links": {
|
|
97
99
|
"choose": "Open a link:",
|
|
@@ -161,5 +163,41 @@
|
|
|
161
163
|
"upToDate": "You are on the latest version ({version})",
|
|
162
164
|
"checkFailed": "Could not check for updates",
|
|
163
165
|
"command": "Run: npm i -g @nbtca/prompt"
|
|
166
|
+
},
|
|
167
|
+
"cli": {
|
|
168
|
+
"usage": "Usage:",
|
|
169
|
+
"interactive": "Interactive menu",
|
|
170
|
+
"runCommand": "Run a command",
|
|
171
|
+
"commands": "Commands:",
|
|
172
|
+
"flags": "Flags:",
|
|
173
|
+
"cmdWebsite": "Official website URL",
|
|
174
|
+
"cmdGithub": "GitHub organization URL",
|
|
175
|
+
"cmdRoadmap": "Project roadmap URL",
|
|
176
|
+
"cmdRepair": "Repair service URL",
|
|
177
|
+
"cmdTheme": "View or set theme",
|
|
178
|
+
"cmdLang": "Set language",
|
|
179
|
+
"cmdUpdate": "Check for updates",
|
|
180
|
+
"flagVersion": "Show version",
|
|
181
|
+
"flagHelp": "Show help",
|
|
182
|
+
"flagOpen": "Open in browser (URL commands)",
|
|
183
|
+
"flagJson": "JSON output (events, status)",
|
|
184
|
+
"flagToday": "Today only (events)",
|
|
185
|
+
"flagNext": "Limit to next N (events)",
|
|
186
|
+
"flagWatch": "Live refresh (status)",
|
|
187
|
+
"flagInterval": "Refresh interval (status --watch)",
|
|
188
|
+
"flagTimeout": "HTTP timeout (status)",
|
|
189
|
+
"flagRetries": "Retry count (status)",
|
|
190
|
+
"flagPlain": "Disable colors",
|
|
191
|
+
"flagNoLogo": "Skip logo",
|
|
192
|
+
"unknownCommand": "Unknown command: {command}",
|
|
193
|
+
"unknownCommandHint": "Run `nbtca --help` to see available commands.",
|
|
194
|
+
"unknownFlag": "Unknown flag: {flag}",
|
|
195
|
+
"unknownFlagHint": "Run `nbtca --help` to see available flags.",
|
|
196
|
+
"invalidFlag": "Flag {flag} is not valid for this command.",
|
|
197
|
+
"invalidFlagHint": "Run `nbtca --help` to see command usage.",
|
|
198
|
+
"invalidLang": "Invalid language. Use `zh` or `en`.",
|
|
199
|
+
"invalidNext": "Invalid --next value. Use --next=<number> (>= 1).",
|
|
200
|
+
"requiresTty": "Interactive mode requires a TTY terminal.",
|
|
201
|
+
"requiresTtyHint": "Use `nbtca --help` for command mode."
|
|
164
202
|
}
|
|
165
203
|
}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"project": "项目",
|
|
29
29
|
"version": "版本",
|
|
30
30
|
"description": "描述",
|
|
31
|
+
"descriptionText": "NBTCA Prompt - 极简命令行工具",
|
|
31
32
|
"github": "GitHub",
|
|
32
33
|
"website": "网站",
|
|
33
34
|
"email": "邮箱",
|
|
@@ -91,7 +92,8 @@
|
|
|
91
92
|
"searchPlaceholder": "输入关键词...",
|
|
92
93
|
"searching": "正在搜索文档...",
|
|
93
94
|
"searchResults": "个结果",
|
|
94
|
-
"searchNoResults": "未找到匹配的文档"
|
|
95
|
+
"searchNoResults": "未找到匹配的文档",
|
|
96
|
+
"loadingFile": "正在加载"
|
|
95
97
|
},
|
|
96
98
|
"links": {
|
|
97
99
|
"choose": "打开链接:",
|
|
@@ -161,5 +163,41 @@
|
|
|
161
163
|
"upToDate": "已是最新版本 ({version})",
|
|
162
164
|
"checkFailed": "无法检查更新",
|
|
163
165
|
"command": "运行: npm i -g @nbtca/prompt"
|
|
166
|
+
},
|
|
167
|
+
"cli": {
|
|
168
|
+
"usage": "用法:",
|
|
169
|
+
"interactive": "交互式菜单",
|
|
170
|
+
"runCommand": "运行命令",
|
|
171
|
+
"commands": "命令:",
|
|
172
|
+
"flags": "选项:",
|
|
173
|
+
"cmdWebsite": "官方网站 URL",
|
|
174
|
+
"cmdGithub": "GitHub 组织 URL",
|
|
175
|
+
"cmdRoadmap": "项目路线图 URL",
|
|
176
|
+
"cmdRepair": "维修服务 URL",
|
|
177
|
+
"cmdTheme": "查看或设置主题",
|
|
178
|
+
"cmdLang": "设置语言",
|
|
179
|
+
"cmdUpdate": "检查更新",
|
|
180
|
+
"flagVersion": "显示版本号",
|
|
181
|
+
"flagHelp": "显示帮助",
|
|
182
|
+
"flagOpen": "在浏览器中打开(URL 命令)",
|
|
183
|
+
"flagJson": "JSON 输出(events, status)",
|
|
184
|
+
"flagToday": "仅显示今日(events)",
|
|
185
|
+
"flagNext": "限制为前 N 个(events)",
|
|
186
|
+
"flagWatch": "实时刷新(status)",
|
|
187
|
+
"flagInterval": "刷新间隔(status --watch)",
|
|
188
|
+
"flagTimeout": "HTTP 超时时间(status)",
|
|
189
|
+
"flagRetries": "重试次数(status)",
|
|
190
|
+
"flagPlain": "禁用颜色",
|
|
191
|
+
"flagNoLogo": "跳过 Logo",
|
|
192
|
+
"unknownCommand": "未知命令: {command}",
|
|
193
|
+
"unknownCommandHint": "运行 `nbtca --help` 查看可用命令。",
|
|
194
|
+
"unknownFlag": "未知选项: {flag}",
|
|
195
|
+
"unknownFlagHint": "运行 `nbtca --help` 查看可用选项。",
|
|
196
|
+
"invalidFlag": "选项 {flag} 对此命令无效。",
|
|
197
|
+
"invalidFlagHint": "运行 `nbtca --help` 查看命令用法。",
|
|
198
|
+
"invalidLang": "无效语言。请使用 `zh` 或 `en`。",
|
|
199
|
+
"invalidNext": "无效的 --next 值。请使用 --next=<数字>(>= 1)。",
|
|
200
|
+
"requiresTty": "交互模式需要 TTY 终端。",
|
|
201
|
+
"requiresTtyHint": "使用 `nbtca --help` 查看命令模式。"
|
|
164
202
|
}
|
|
165
203
|
}
|
package/dist/index.js
CHANGED
|
@@ -10,8 +10,8 @@ import { pickIcon } from './core/icons.js';
|
|
|
10
10
|
import { applyColorModePreference } from './config/preferences.js';
|
|
11
11
|
import { openDocsInBrowser } from './features/docs.js';
|
|
12
12
|
import { runThemeCommand } from './features/theme.js';
|
|
13
|
-
import { setLanguage, t } from './i18n/index.js';
|
|
14
|
-
import { clearScreen } from './core/ui.js';
|
|
13
|
+
import { setLanguage, t, fmt } from './i18n/index.js';
|
|
14
|
+
import { clearScreen, handleGracefulExit } from './core/ui.js';
|
|
15
15
|
import { APP_INFO, URLS } from './config/data.js';
|
|
16
16
|
import { runUpdateCheck } from './features/update.js';
|
|
17
17
|
const ACTION_ALIASES = {
|
|
@@ -115,8 +115,9 @@ function validateFlags(command, flags) {
|
|
|
115
115
|
return !KNOWN_FLAG_PREFIXES.some((prefix) => flag.startsWith(prefix));
|
|
116
116
|
});
|
|
117
117
|
if (unknown.length > 0) {
|
|
118
|
-
|
|
119
|
-
console.error(chalk.
|
|
118
|
+
const trans0 = t();
|
|
119
|
+
console.error(chalk.red(fmt(trans0.cli.unknownFlag, { flag: unknown[0] })));
|
|
120
|
+
console.error(chalk.dim(trans0.cli.unknownFlagHint));
|
|
120
121
|
process.exit(1);
|
|
121
122
|
}
|
|
122
123
|
const allowed = getAllowedFlagsFor(command);
|
|
@@ -127,56 +128,63 @@ function validateFlags(command, flags) {
|
|
|
127
128
|
return !allowedPrefixes.some((prefix) => flag.startsWith(prefix));
|
|
128
129
|
});
|
|
129
130
|
if (disallowed.length > 0) {
|
|
130
|
-
|
|
131
|
-
console.error(chalk.
|
|
131
|
+
const trans1 = t();
|
|
132
|
+
console.error(chalk.red(fmt(trans1.cli.invalidFlag, { flag: disallowed[0] })));
|
|
133
|
+
console.error(chalk.dim(trans1.cli.invalidFlagHint));
|
|
132
134
|
process.exit(1);
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
function printHelp() {
|
|
138
|
+
const trans = t();
|
|
139
|
+
const c = trans.cli;
|
|
136
140
|
console.log(chalk.bold('NBTCA Prompt'));
|
|
137
141
|
console.log();
|
|
138
|
-
console.log(
|
|
139
|
-
console.log(
|
|
140
|
-
console.log(
|
|
142
|
+
console.log(c.usage);
|
|
143
|
+
console.log(` nbtca ${c.interactive}`);
|
|
144
|
+
console.log(` nbtca <command> [flags] ${c.runCommand}`);
|
|
141
145
|
console.log();
|
|
142
|
-
console.log(
|
|
143
|
-
console.log(
|
|
144
|
-
console.log(
|
|
145
|
-
console.log(
|
|
146
|
-
console.log(
|
|
147
|
-
console.log(
|
|
148
|
-
console.log(
|
|
149
|
-
console.log(
|
|
150
|
-
console.log(
|
|
151
|
-
console.log(
|
|
152
|
-
console.log(
|
|
146
|
+
console.log(c.commands);
|
|
147
|
+
console.log(` events ${trans.menu.eventsDesc}`);
|
|
148
|
+
console.log(` docs ${trans.menu.docsDesc}`);
|
|
149
|
+
console.log(` status ${trans.menu.statusDesc}`);
|
|
150
|
+
console.log(` website ${c.cmdWebsite}`);
|
|
151
|
+
console.log(` github ${c.cmdGithub}`);
|
|
152
|
+
console.log(` roadmap ${c.cmdRoadmap}`);
|
|
153
|
+
console.log(` repair ${c.cmdRepair}`);
|
|
154
|
+
console.log(` theme ${c.cmdTheme}`);
|
|
155
|
+
console.log(` lang <zh|en> ${c.cmdLang}`);
|
|
156
|
+
console.log(` update ${c.cmdUpdate}`);
|
|
153
157
|
console.log();
|
|
154
|
-
console.log(
|
|
155
|
-
console.log(
|
|
156
|
-
console.log(
|
|
157
|
-
console.log(
|
|
158
|
-
console.log(
|
|
159
|
-
console.log(
|
|
160
|
-
console.log(
|
|
161
|
-
console.log(
|
|
162
|
-
console.log(
|
|
163
|
-
console.log(
|
|
164
|
-
console.log(
|
|
165
|
-
console.log(
|
|
166
|
-
console.log(
|
|
158
|
+
console.log(c.flags);
|
|
159
|
+
console.log(` --version ${c.flagVersion}`);
|
|
160
|
+
console.log(` --help ${c.flagHelp}`);
|
|
161
|
+
console.log(` --open ${c.flagOpen}`);
|
|
162
|
+
console.log(` --json ${c.flagJson}`);
|
|
163
|
+
console.log(` --today ${c.flagToday}`);
|
|
164
|
+
console.log(` --next=<n> ${c.flagNext}`);
|
|
165
|
+
console.log(` --watch ${c.flagWatch}`);
|
|
166
|
+
console.log(` --interval=<s> ${c.flagInterval}`);
|
|
167
|
+
console.log(` --timeout=<ms> ${c.flagTimeout}`);
|
|
168
|
+
console.log(` --retries=<n> ${c.flagRetries}`);
|
|
169
|
+
console.log(` --plain ${c.flagPlain}`);
|
|
170
|
+
console.log(` --no-logo ${c.flagNoLogo}`);
|
|
167
171
|
}
|
|
168
172
|
async function runEventsCommand(flags) {
|
|
169
173
|
let events = await fetchEvents();
|
|
170
174
|
if (flags.has('--today')) {
|
|
171
175
|
const now = new Date();
|
|
172
|
-
|
|
173
|
-
|
|
176
|
+
events = events.filter(e => {
|
|
177
|
+
const d = e.startDate;
|
|
178
|
+
return d.getFullYear() === now.getFullYear() &&
|
|
179
|
+
d.getMonth() === now.getMonth() &&
|
|
180
|
+
d.getDate() === now.getDate();
|
|
181
|
+
});
|
|
174
182
|
}
|
|
175
183
|
const nextFlag = Array.from(flags).find(f => f.startsWith('--next='));
|
|
176
184
|
if (nextFlag) {
|
|
177
185
|
const n = Number.parseInt(nextFlag.split('=')[1] || '', 10);
|
|
178
186
|
if (!Number.isInteger(n) || n < 1) {
|
|
179
|
-
console.error(chalk.red(
|
|
187
|
+
console.error(chalk.red(t().cli.invalidNext));
|
|
180
188
|
process.exit(1);
|
|
181
189
|
}
|
|
182
190
|
events = events.slice(0, n);
|
|
@@ -202,11 +210,11 @@ async function runStatusCommand(flags) {
|
|
|
202
210
|
process.exit(1);
|
|
203
211
|
}
|
|
204
212
|
if (!Number.isInteger(timeoutMs) || timeoutMs < STATUS_TIMEOUT_MIN || timeoutMs > STATUS_TIMEOUT_MAX) {
|
|
205
|
-
console.error(chalk.red(trans.status.invalidTimeout
|
|
213
|
+
console.error(chalk.red(fmt(trans.status.invalidTimeout, { min: STATUS_TIMEOUT_MIN, max: STATUS_TIMEOUT_MAX })));
|
|
206
214
|
process.exit(1);
|
|
207
215
|
}
|
|
208
216
|
if (!Number.isInteger(retries) || retries < STATUS_RETRIES_MIN || retries > STATUS_RETRIES_MAX) {
|
|
209
|
-
console.error(chalk.red(trans.status.invalidRetries
|
|
217
|
+
console.error(chalk.red(fmt(trans.status.invalidRetries, { min: STATUS_RETRIES_MIN, max: STATUS_RETRIES_MAX })));
|
|
210
218
|
process.exit(1);
|
|
211
219
|
}
|
|
212
220
|
if (watch && flags.has('--json')) {
|
|
@@ -214,7 +222,7 @@ async function runStatusCommand(flags) {
|
|
|
214
222
|
process.exit(1);
|
|
215
223
|
}
|
|
216
224
|
if (watch && (!Number.isInteger(intervalSeconds) || intervalSeconds < STATUS_WATCH_INTERVAL_MIN || intervalSeconds > STATUS_WATCH_INTERVAL_MAX)) {
|
|
217
|
-
console.error(chalk.red(trans.status.invalidInterval
|
|
225
|
+
console.error(chalk.red(fmt(trans.status.invalidInterval, { min: STATUS_WATCH_INTERVAL_MIN, max: STATUS_WATCH_INTERVAL_MAX })));
|
|
218
226
|
process.exit(1);
|
|
219
227
|
}
|
|
220
228
|
if (watch && !hasInteractiveTerminal()) {
|
|
@@ -225,7 +233,7 @@ async function runStatusCommand(flags) {
|
|
|
225
233
|
let stopped = false;
|
|
226
234
|
const onSigint = () => { stopped = true; };
|
|
227
235
|
process.once('SIGINT', onSigint);
|
|
228
|
-
console.log(chalk.dim(`${trans.status.watchStarted
|
|
236
|
+
console.log(chalk.dim(`${fmt(trans.status.watchStarted, { seconds: intervalSeconds })} | ${trans.status.watchHint}`));
|
|
229
237
|
try {
|
|
230
238
|
while (!stopped) {
|
|
231
239
|
const services = await checkServices({ timeoutMs, retries });
|
|
@@ -297,8 +305,9 @@ async function runCommandMode(argv) {
|
|
|
297
305
|
validateFlags(command, flags);
|
|
298
306
|
if (!command) {
|
|
299
307
|
if (!hasInteractiveTerminal()) {
|
|
300
|
-
|
|
301
|
-
console.error(chalk.
|
|
308
|
+
const cliTrans = t().cli;
|
|
309
|
+
console.error(chalk.red(cliTrans.requiresTty));
|
|
310
|
+
console.error(chalk.dim(cliTrans.requiresTtyHint));
|
|
302
311
|
process.exit(1);
|
|
303
312
|
}
|
|
304
313
|
await main({ skipLogo: flags.has('--no-logo') });
|
|
@@ -307,7 +316,7 @@ async function runCommandMode(argv) {
|
|
|
307
316
|
if (command === 'lang' || command === 'language') {
|
|
308
317
|
const language = (args[0] || '').toLowerCase();
|
|
309
318
|
if (language !== 'zh' && language !== 'en') {
|
|
310
|
-
console.error(chalk.red(
|
|
319
|
+
console.error(chalk.red(t().cli.invalidLang));
|
|
311
320
|
process.exit(1);
|
|
312
321
|
}
|
|
313
322
|
const persisted = setLanguage(language);
|
|
@@ -336,8 +345,9 @@ async function runCommandMode(argv) {
|
|
|
336
345
|
}
|
|
337
346
|
const action = ACTION_ALIASES[command];
|
|
338
347
|
if (!action) {
|
|
339
|
-
|
|
340
|
-
console.error(chalk.
|
|
348
|
+
const cliT = t().cli;
|
|
349
|
+
console.error(chalk.red(fmt(cliT.unknownCommand, { command })));
|
|
350
|
+
console.error(chalk.dim(cliT.unknownCommandHint));
|
|
341
351
|
process.exit(1);
|
|
342
352
|
}
|
|
343
353
|
if (action === 'events') {
|
|
@@ -346,7 +356,7 @@ async function runCommandMode(argv) {
|
|
|
346
356
|
}
|
|
347
357
|
if (action === 'status') {
|
|
348
358
|
const ok = await runStatusCommand(flags);
|
|
349
|
-
if (!ok)
|
|
359
|
+
if (!ok && !flags.has('--json'))
|
|
350
360
|
process.exit(1);
|
|
351
361
|
return;
|
|
352
362
|
}
|
|
@@ -375,9 +385,13 @@ async function runCommandMode(argv) {
|
|
|
375
385
|
const content = [
|
|
376
386
|
row(trans.about.project, APP_INFO.name),
|
|
377
387
|
row(trans.about.version, `v${APP_INFO.version}`),
|
|
388
|
+
row(trans.about.description, trans.about.descriptionText),
|
|
378
389
|
'',
|
|
379
390
|
link(trans.about.github, APP_INFO.repository),
|
|
380
391
|
link(trans.about.website, URLS.homepage),
|
|
392
|
+
link(trans.about.email, URLS.email),
|
|
393
|
+
'',
|
|
394
|
+
row(trans.about.license, `MIT | ${trans.about.author}: m1ngsama`),
|
|
381
395
|
].join('\n');
|
|
382
396
|
note(content, trans.about.title);
|
|
383
397
|
return;
|
|
@@ -394,17 +408,4 @@ async function runCommandMode(argv) {
|
|
|
394
408
|
return;
|
|
395
409
|
}
|
|
396
410
|
}
|
|
397
|
-
runCommandMode(process.argv.slice(2)).catch(
|
|
398
|
-
if (err?.message?.includes('SIGINT') || err?.message?.includes('User force closed')) {
|
|
399
|
-
console.log();
|
|
400
|
-
console.log(chalk.dim(t().common.goodbye));
|
|
401
|
-
process.exit(0);
|
|
402
|
-
}
|
|
403
|
-
if (err?.message) {
|
|
404
|
-
console.error(err.message);
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
console.error('Error occurred:', err);
|
|
408
|
-
}
|
|
409
|
-
process.exit(1);
|
|
410
|
-
});
|
|
411
|
+
runCommandMode(process.argv.slice(2)).catch(handleGracefulExit);
|
package/dist/main.js
CHANGED
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { intro } from '@clack/prompts';
|
|
7
7
|
import { printLogo } from './core/logo.js';
|
|
8
|
-
import { clearScreen } from './core/ui.js';
|
|
8
|
+
import { clearScreen, handleGracefulExit } from './core/ui.js';
|
|
9
9
|
import { showMainMenu } from './core/menu.js';
|
|
10
10
|
import { APP_INFO } from './config/data.js';
|
|
11
11
|
import { enableVimKeys } from './core/vim-keys.js';
|
|
12
|
-
import { t } from './i18n/index.js';
|
|
13
12
|
import { checkForUpdate } from './features/update.js';
|
|
14
13
|
/**
|
|
15
14
|
* Main program entry point
|
|
@@ -41,15 +40,6 @@ export async function main(options = {}) {
|
|
|
41
40
|
await showMainMenu();
|
|
42
41
|
}
|
|
43
42
|
catch (err) {
|
|
44
|
-
|
|
45
|
-
if (message.includes('SIGINT') || message.includes('User force closed')) {
|
|
46
|
-
console.log();
|
|
47
|
-
console.log(chalk.dim(t().common.goodbye));
|
|
48
|
-
process.exit(0);
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
console.error('Error occurred:', message || err);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
43
|
+
handleGracefulExit(err);
|
|
54
44
|
}
|
|
55
45
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nbtca/prompt",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dev": "tsx src/index.ts",
|
|
22
22
|
"dev:watch": "tsx watch src/index.ts",
|
|
23
23
|
"build": "tsc",
|
|
24
|
-
"postbuild": "
|
|
24
|
+
"postbuild": "node scripts/copy-assets.js",
|
|
25
25
|
"clean": "rm -rf dist",
|
|
26
26
|
"prebuild": "npm run clean",
|
|
27
27
|
"prepublishOnly": "npm run build",
|
|
@@ -38,21 +38,20 @@
|
|
|
38
38
|
"interactive"
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@clack/prompts": "^1.0
|
|
42
|
-
"
|
|
43
|
-
"chalk": "^5.4.1",
|
|
41
|
+
"@clack/prompts": "^1.2.0",
|
|
42
|
+
"chalk": "^5.6.2",
|
|
44
43
|
"gradient-string": "^3.0.0",
|
|
45
|
-
"ical.js": "^2.
|
|
46
|
-
"marked": "^
|
|
44
|
+
"ical.js": "^2.2.1",
|
|
45
|
+
"marked": "^15.0.12",
|
|
47
46
|
"marked-terminal": "^7.0.0",
|
|
48
|
-
"open": "^
|
|
47
|
+
"open": "^11.0.0"
|
|
49
48
|
},
|
|
50
49
|
"devDependencies": {
|
|
51
50
|
"@types/gradient-string": "^1.1.6",
|
|
52
51
|
"@types/marked-terminal": "^3.1.3",
|
|
53
|
-
"@types/node": "^
|
|
54
|
-
"tsx": "^4.
|
|
55
|
-
"typescript": "^5.
|
|
52
|
+
"@types/node": "^22.19.17",
|
|
53
|
+
"tsx": "^4.21.0",
|
|
54
|
+
"typescript": "^5.9.3"
|
|
56
55
|
},
|
|
57
56
|
"engines": {
|
|
58
57
|
"node": ">=20.12.0"
|