@nbtca/prompt 1.4.1 → 1.5.0
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/LICENSE +1 -1
- package/README.md +27 -58
- package/SECURITY.md +16 -45
- package/dist/app/app.js +53 -55
- package/dist/app/chrome.js +67 -50
- package/dist/app/fields/list-field.js +12 -25
- package/dist/app/fields/text-field.js +3 -8
- package/dist/app/frame.js +2 -21
- package/dist/app/keys.js +10 -2
- package/dist/app/views/docs-render.js +31 -24
- package/dist/app/views/docs.js +211 -67
- package/dist/app/views/events-render.js +19 -26
- package/dist/app/views/events.js +44 -31
- package/dist/app/views/home.js +33 -76
- package/dist/app/views/schedule-grid-cursor.js +9 -18
- package/dist/app/views/schedule-render.js +47 -71
- package/dist/app/views/schedule.js +158 -90
- package/dist/app/views/settings-render.js +8 -19
- package/dist/app/views/settings.js +93 -18
- package/dist/auth/cookie-transport.js +31 -32
- package/dist/auth/errors.js +3 -1
- package/dist/auth/nbt-auth.js +42 -25
- package/dist/auth/session-store.js +17 -9
- package/dist/config/data.js +10 -13
- package/dist/config/preferences.js +14 -7
- package/dist/core/calendar-day.js +37 -0
- package/dist/core/capabilities.js +6 -3
- package/dist/core/components/confirm.js +9 -8
- package/dist/core/components/menu.js +41 -16
- package/dist/core/components/messages.js +12 -4
- package/dist/core/components/painter.js +3 -1
- package/dist/core/components/spinner.js +17 -6
- package/dist/core/components/text-input.js +24 -18
- package/dist/core/icons.js +2 -2
- package/dist/core/logo.js +25 -21
- package/dist/core/motion.js +25 -19
- package/dist/core/text.js +182 -75
- package/dist/core/theme.js +0 -28
- package/dist/core/transitions.js +2 -2
- package/dist/core/ui.js +15 -30
- package/dist/core/vim-keys.js +9 -15
- package/dist/features/about.js +23 -0
- package/dist/features/calendar-heatmap.js +16 -40
- package/dist/features/calendar-query.js +1 -2
- package/dist/features/calendar.js +12 -185
- package/dist/features/docs.js +439 -320
- package/dist/features/schedule-render.js +65 -102
- package/dist/features/schedule-store.js +51 -9
- package/dist/features/schedule-view.js +46 -220
- package/dist/features/status.js +44 -59
- package/dist/features/student-timetable.js +73 -95
- package/dist/features/theme.js +6 -5
- package/dist/features/timetable-sanitize.js +40 -0
- package/dist/features/update.js +9 -37
- package/dist/i18n/index.js +87 -65
- package/dist/i18n/locales/en.json +1 -1
- package/dist/i18n/locales/zh.json +1 -1
- package/dist/index.js +85 -64
- package/dist/logo/ca-dotmatrix.txt +16 -18
- package/dist/main.js +7 -48
- package/package.json +30 -18
- package/bin/nbtca-welcome.js +0 -2
- package/dist/core/components/screen.js +0 -18
- package/dist/core/menu.js +0 -71
- package/dist/features/links.js +0 -39
- package/dist/features/schedule-query.js +0 -47
- package/dist/features/settings.js +0 -130
- package/dist/logo/ca-logo.png +0 -0
package/dist/index.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
* NBTCA Prompt entry point
|
|
3
|
-
*/
|
|
1
|
+
#!/usr/bin/env node
|
|
4
2
|
import chalk from 'chalk';
|
|
5
3
|
import open from 'open';
|
|
6
4
|
import { main } from './main.js';
|
|
7
|
-
import { fetchEvents, fetchHeatmapBuckets, renderEventsTable, serializeEvents } from './features/calendar.js';
|
|
5
|
+
import { fetchEvents, fetchHeatmapBuckets, renderEventsTable, serializeEvents, } from './features/calendar.js';
|
|
8
6
|
import { renderHeatmap } from './features/calendar-heatmap.js';
|
|
9
|
-
import { checkServices, countServiceHealth, hasServiceFailures, renderServiceStatusTable, serializeServiceStatus } from './features/status.js';
|
|
7
|
+
import { checkServices, countServiceHealth, hasServiceFailures, renderServiceStatusTable, serializeServiceStatus, } from './features/status.js';
|
|
10
8
|
import { pickIcon } from './core/icons.js';
|
|
11
9
|
import { applyColorModePreference } from './config/preferences.js';
|
|
12
10
|
import { openDocsInBrowser } from './features/docs.js';
|
|
13
11
|
import { runThemeCommand } from './features/theme.js';
|
|
14
|
-
import {
|
|
12
|
+
import { saveLanguagePreference, t, fmt } from './i18n/index.js';
|
|
15
13
|
import { clearScreen, handleGracefulExit } from './core/ui.js';
|
|
16
14
|
import { APP_INFO, URLS } from './config/data.js';
|
|
17
15
|
import { runUpdateCheck } from './features/update.js';
|
|
18
16
|
import { runStudentTimetableCommand } from './features/student-timetable.js';
|
|
17
|
+
import { showAbout } from './features/about.js';
|
|
19
18
|
const ACTION_ALIASES = {
|
|
20
19
|
events: 'events',
|
|
21
20
|
event: 'events',
|
|
@@ -35,13 +34,33 @@ const URL_ACTIONS = {
|
|
|
35
34
|
repair: URLS.repair,
|
|
36
35
|
website: URLS.homepage,
|
|
37
36
|
github: URLS.github,
|
|
38
|
-
roadmap: URLS.roadmap
|
|
37
|
+
roadmap: URLS.roadmap,
|
|
39
38
|
};
|
|
40
39
|
const KNOWN_FLAGS = new Set([
|
|
41
|
-
'--help',
|
|
42
|
-
'--
|
|
40
|
+
'--help',
|
|
41
|
+
'--version',
|
|
42
|
+
'--open',
|
|
43
|
+
'--json',
|
|
44
|
+
'--plain',
|
|
45
|
+
'--no-logo',
|
|
46
|
+
'--watch',
|
|
47
|
+
'--today',
|
|
48
|
+
'--heatmap',
|
|
49
|
+
'--week',
|
|
50
|
+
'--month',
|
|
51
|
+
'--one-shot',
|
|
52
|
+
'--no-save',
|
|
43
53
|
]);
|
|
44
|
-
const KNOWN_FLAG_PREFIXES = [
|
|
54
|
+
const KNOWN_FLAG_PREFIXES = [
|
|
55
|
+
'--interval=',
|
|
56
|
+
'--timeout=',
|
|
57
|
+
'--retries=',
|
|
58
|
+
'--next=',
|
|
59
|
+
'--search=',
|
|
60
|
+
'--term=',
|
|
61
|
+
'--output=',
|
|
62
|
+
'--week-one=',
|
|
63
|
+
];
|
|
45
64
|
const STATUS_WATCH_INTERVAL_MIN = 3;
|
|
46
65
|
const STATUS_WATCH_INTERVAL_MAX = 300;
|
|
47
66
|
const STATUS_TIMEOUT_MIN = 1000;
|
|
@@ -60,13 +79,16 @@ function parseArgs(argv) {
|
|
|
60
79
|
}
|
|
61
80
|
}
|
|
62
81
|
return {
|
|
63
|
-
command: positionals[0]
|
|
82
|
+
...(positionals[0] === undefined ? {} : { command: positionals[0].toLowerCase() }),
|
|
64
83
|
args: positionals.slice(1),
|
|
65
|
-
flags
|
|
84
|
+
flags,
|
|
66
85
|
};
|
|
67
86
|
}
|
|
87
|
+
function isTty(value) {
|
|
88
|
+
return value === true;
|
|
89
|
+
}
|
|
68
90
|
function hasInteractiveTerminal() {
|
|
69
|
-
return
|
|
91
|
+
return isTty(process.stdin.isTTY) && isTty(process.stdout.isTTY);
|
|
70
92
|
}
|
|
71
93
|
function getAllowedFlagsFor(command) {
|
|
72
94
|
const allowed = new Set(['--help', '--plain']);
|
|
@@ -125,27 +147,27 @@ function getAllowedFlagPrefixesFor(command) {
|
|
|
125
147
|
return [];
|
|
126
148
|
}
|
|
127
149
|
function validateFlags(command, flags) {
|
|
128
|
-
const
|
|
150
|
+
const unknownFlag = Array.from(flags).find((flag) => {
|
|
129
151
|
if (KNOWN_FLAGS.has(flag))
|
|
130
152
|
return false;
|
|
131
153
|
return !KNOWN_FLAG_PREFIXES.some((prefix) => flag.startsWith(prefix));
|
|
132
154
|
});
|
|
133
|
-
if (
|
|
155
|
+
if (unknownFlag) {
|
|
134
156
|
const trans0 = t();
|
|
135
|
-
console.error(chalk.red(fmt(trans0.cli.unknownFlag, { flag:
|
|
157
|
+
console.error(chalk.red(fmt(trans0.cli.unknownFlag, { flag: unknownFlag })));
|
|
136
158
|
console.error(chalk.dim(trans0.cli.unknownFlagHint));
|
|
137
159
|
process.exit(1);
|
|
138
160
|
}
|
|
139
161
|
const allowed = getAllowedFlagsFor(command);
|
|
140
162
|
const allowedPrefixes = getAllowedFlagPrefixesFor(command);
|
|
141
|
-
const
|
|
163
|
+
const disallowedFlag = Array.from(flags).find((flag) => {
|
|
142
164
|
if (allowed.has(flag))
|
|
143
165
|
return false;
|
|
144
166
|
return !allowedPrefixes.some((prefix) => flag.startsWith(prefix));
|
|
145
167
|
});
|
|
146
|
-
if (
|
|
168
|
+
if (disallowedFlag) {
|
|
147
169
|
const trans1 = t();
|
|
148
|
-
console.error(chalk.red(fmt(trans1.cli.invalidFlag, { flag:
|
|
170
|
+
console.error(chalk.red(fmt(trans1.cli.invalidFlag, { flag: disallowedFlag })));
|
|
149
171
|
console.error(chalk.dim(trans1.cli.invalidFlagHint));
|
|
150
172
|
process.exit(1);
|
|
151
173
|
}
|
|
@@ -203,7 +225,7 @@ async function runEventsCommand(flags) {
|
|
|
203
225
|
process.stdout.write(JSON.stringify(buckets, null, 2) + '\n');
|
|
204
226
|
}
|
|
205
227
|
else {
|
|
206
|
-
const useColor = !flags.has('--plain') &&
|
|
228
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
207
229
|
console.log(renderHeatmap(buckets, new Date(), { color: useColor }));
|
|
208
230
|
}
|
|
209
231
|
return;
|
|
@@ -223,23 +245,23 @@ async function runEventsCommand(flags) {
|
|
|
223
245
|
else {
|
|
224
246
|
events = await fetchEvents();
|
|
225
247
|
}
|
|
226
|
-
const searchFlag = Array.from(flags).find(f => f.startsWith('--search='));
|
|
248
|
+
const searchFlag = Array.from(flags).find((f) => f.startsWith('--search='));
|
|
227
249
|
if (searchFlag) {
|
|
228
250
|
const q = searchFlag.slice('--search='.length).toLowerCase();
|
|
229
|
-
events = events.filter(e => `${e.title} ${e.location}`.toLowerCase().includes(q));
|
|
251
|
+
events = events.filter((e) => `${e.title} ${e.location}`.toLowerCase().includes(q));
|
|
230
252
|
}
|
|
231
253
|
if (flags.has('--today')) {
|
|
232
254
|
const now = new Date();
|
|
233
|
-
events = events.filter(e => {
|
|
255
|
+
events = events.filter((e) => {
|
|
234
256
|
const d = e.startDate;
|
|
235
|
-
return d.getFullYear() === now.getFullYear() &&
|
|
257
|
+
return (d.getFullYear() === now.getFullYear() &&
|
|
236
258
|
d.getMonth() === now.getMonth() &&
|
|
237
|
-
d.getDate() === now.getDate();
|
|
259
|
+
d.getDate() === now.getDate());
|
|
238
260
|
});
|
|
239
261
|
}
|
|
240
|
-
const nextFlag = Array.from(flags).find(f => f.startsWith('--next='));
|
|
262
|
+
const nextFlag = Array.from(flags).find((f) => f.startsWith('--next='));
|
|
241
263
|
if (nextFlag) {
|
|
242
|
-
const n = Number.parseInt(nextFlag.
|
|
264
|
+
const n = Number.parseInt(nextFlag.slice('--next='.length), 10);
|
|
243
265
|
if (!Number.isInteger(n) || n < 1) {
|
|
244
266
|
console.error(chalk.red(t().cli.invalidNext));
|
|
245
267
|
process.exit(1);
|
|
@@ -250,7 +272,7 @@ async function runEventsCommand(flags) {
|
|
|
250
272
|
process.stdout.write(JSON.stringify(serializeEvents(events), null, 2) + '\n');
|
|
251
273
|
return;
|
|
252
274
|
}
|
|
253
|
-
const useColor = !flags.has('--plain') &&
|
|
275
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
254
276
|
console.log(renderEventsTable(events, { color: useColor }));
|
|
255
277
|
}
|
|
256
278
|
async function runStatusCommand(flags) {
|
|
@@ -259,14 +281,20 @@ async function runStatusCommand(flags) {
|
|
|
259
281
|
const intervalFlag = Array.from(flags).find((flag) => flag.startsWith('--interval='));
|
|
260
282
|
const timeoutFlag = Array.from(flags).find((flag) => flag.startsWith('--timeout='));
|
|
261
283
|
const retriesFlag = Array.from(flags).find((flag) => flag.startsWith('--retries='));
|
|
262
|
-
const intervalSeconds = intervalFlag
|
|
263
|
-
|
|
264
|
-
|
|
284
|
+
const intervalSeconds = intervalFlag
|
|
285
|
+
? Number.parseInt(intervalFlag.slice('--interval='.length), 10)
|
|
286
|
+
: 10;
|
|
287
|
+
const timeoutMs = timeoutFlag
|
|
288
|
+
? Number.parseInt(timeoutFlag.slice('--timeout='.length), 10)
|
|
289
|
+
: 6000;
|
|
290
|
+
const retries = retriesFlag ? Number.parseInt(retriesFlag.slice('--retries='.length), 10) : 1;
|
|
265
291
|
if (!watch && intervalFlag) {
|
|
266
292
|
console.error(chalk.red(trans.status.intervalNeedsWatch));
|
|
267
293
|
process.exit(1);
|
|
268
294
|
}
|
|
269
|
-
if (!Number.isInteger(timeoutMs) ||
|
|
295
|
+
if (!Number.isInteger(timeoutMs) ||
|
|
296
|
+
timeoutMs < STATUS_TIMEOUT_MIN ||
|
|
297
|
+
timeoutMs > STATUS_TIMEOUT_MAX) {
|
|
270
298
|
console.error(chalk.red(fmt(trans.status.invalidTimeout, { min: STATUS_TIMEOUT_MIN, max: STATUS_TIMEOUT_MAX })));
|
|
271
299
|
process.exit(1);
|
|
272
300
|
}
|
|
@@ -278,8 +306,14 @@ async function runStatusCommand(flags) {
|
|
|
278
306
|
console.error(chalk.red(trans.status.watchJsonConflict));
|
|
279
307
|
process.exit(1);
|
|
280
308
|
}
|
|
281
|
-
if (watch &&
|
|
282
|
-
|
|
309
|
+
if (watch &&
|
|
310
|
+
(!Number.isInteger(intervalSeconds) ||
|
|
311
|
+
intervalSeconds < STATUS_WATCH_INTERVAL_MIN ||
|
|
312
|
+
intervalSeconds > STATUS_WATCH_INTERVAL_MAX)) {
|
|
313
|
+
console.error(chalk.red(fmt(trans.status.invalidInterval, {
|
|
314
|
+
min: STATUS_WATCH_INTERVAL_MIN,
|
|
315
|
+
max: STATUS_WATCH_INTERVAL_MAX,
|
|
316
|
+
})));
|
|
283
317
|
process.exit(1);
|
|
284
318
|
}
|
|
285
319
|
if (watch && !hasInteractiveTerminal()) {
|
|
@@ -287,12 +321,14 @@ async function runStatusCommand(flags) {
|
|
|
287
321
|
process.exit(1);
|
|
288
322
|
}
|
|
289
323
|
if (watch) {
|
|
290
|
-
|
|
291
|
-
const onSigint = () => {
|
|
324
|
+
const stopController = new AbortController();
|
|
325
|
+
const onSigint = () => {
|
|
326
|
+
stopController.abort();
|
|
327
|
+
};
|
|
292
328
|
process.once('SIGINT', onSigint);
|
|
293
329
|
console.log(chalk.dim(`${fmt(trans.status.watchStarted, { seconds: intervalSeconds })} | ${trans.status.watchHint}`));
|
|
294
330
|
try {
|
|
295
|
-
while (!
|
|
331
|
+
while (!stopController.signal.aborted) {
|
|
296
332
|
const services = await checkServices({ timeoutMs, retries });
|
|
297
333
|
const hasFailures = hasServiceFailures(services);
|
|
298
334
|
const health = countServiceHealth(services);
|
|
@@ -300,7 +336,7 @@ async function runStatusCommand(flags) {
|
|
|
300
336
|
console.log(chalk.bold(`${trans.status.watchUpdated}: ${new Date().toLocaleString()}`));
|
|
301
337
|
console.log(chalk.dim(`${trans.status.up}: ${health.up} | ${trans.status.down}: ${health.down} | ${trans.status.watchHint}`));
|
|
302
338
|
console.log();
|
|
303
|
-
const useColor = !flags.has('--plain') &&
|
|
339
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
304
340
|
console.log(renderServiceStatusTable(services, { color: useColor }));
|
|
305
341
|
if (hasFailures) {
|
|
306
342
|
console.log(chalk.yellow(trans.status.summaryFail));
|
|
@@ -311,12 +347,12 @@ async function runStatusCommand(flags) {
|
|
|
311
347
|
await new Promise((resolve) => {
|
|
312
348
|
const stopWait = () => {
|
|
313
349
|
clearTimeout(timer);
|
|
314
|
-
|
|
350
|
+
stopController.signal.removeEventListener('abort', stopWait);
|
|
315
351
|
resolve();
|
|
316
352
|
};
|
|
317
353
|
const timer = setTimeout(stopWait, intervalSeconds * 1000);
|
|
318
|
-
|
|
319
|
-
if (
|
|
354
|
+
stopController.signal.addEventListener('abort', stopWait, { once: true });
|
|
355
|
+
if (stopController.signal.aborted)
|
|
320
356
|
stopWait();
|
|
321
357
|
});
|
|
322
358
|
}
|
|
@@ -334,7 +370,7 @@ async function runStatusCommand(flags) {
|
|
|
334
370
|
process.stdout.write(JSON.stringify(serializeServiceStatus(services), null, 2) + '\n');
|
|
335
371
|
}
|
|
336
372
|
else {
|
|
337
|
-
const useColor = !flags.has('--plain') &&
|
|
373
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
338
374
|
console.log(renderServiceStatusTable(services, { color: useColor }));
|
|
339
375
|
if (hasFailures) {
|
|
340
376
|
console.error(chalk.yellow(t().status.summaryFail));
|
|
@@ -351,7 +387,10 @@ function maybeDisableColor(flags) {
|
|
|
351
387
|
async function runCommandMode(argv) {
|
|
352
388
|
const { command, args, flags } = parseArgs(argv);
|
|
353
389
|
maybeDisableColor(flags);
|
|
354
|
-
if (flags.has('--version') ||
|
|
390
|
+
if (flags.has('--version') ||
|
|
391
|
+
command === '--version' ||
|
|
392
|
+
command === '-v' ||
|
|
393
|
+
command === 'version') {
|
|
355
394
|
console.log(APP_INFO.version);
|
|
356
395
|
return;
|
|
357
396
|
}
|
|
@@ -371,12 +410,12 @@ async function runCommandMode(argv) {
|
|
|
371
410
|
return;
|
|
372
411
|
}
|
|
373
412
|
if (command === 'lang' || command === 'language') {
|
|
374
|
-
const language = (args[0]
|
|
413
|
+
const language = (args[0] ?? '').toLowerCase();
|
|
375
414
|
if (language !== 'zh' && language !== 'en') {
|
|
376
415
|
console.error(chalk.red(t().cli.invalidLang));
|
|
377
416
|
process.exit(1);
|
|
378
417
|
}
|
|
379
|
-
const persisted =
|
|
418
|
+
const persisted = saveLanguagePreference(language);
|
|
380
419
|
if (persisted) {
|
|
381
420
|
console.log(chalk.green(`${pickIcon('✓', 'OK')}: ${t().language.changed}`));
|
|
382
421
|
}
|
|
@@ -444,27 +483,9 @@ async function runCommandMode(argv) {
|
|
|
444
483
|
return;
|
|
445
484
|
}
|
|
446
485
|
if (action === 'about') {
|
|
447
|
-
|
|
448
|
-
const { padEndV } = await import('./core/text.js');
|
|
449
|
-
const pad = 12;
|
|
450
|
-
const row = (label, value) => `${chalk.dim(padEndV(label, pad))}${value}`;
|
|
451
|
-
const link = (label, url) => row(label, chalk.cyan(url));
|
|
452
|
-
const trans = t();
|
|
453
|
-
const content = [
|
|
454
|
-
row(trans.about.project, APP_INFO.name),
|
|
455
|
-
row(trans.about.version, `v${APP_INFO.version}`),
|
|
456
|
-
row(trans.about.description, trans.about.descriptionText),
|
|
457
|
-
'',
|
|
458
|
-
link(trans.about.github, APP_INFO.repository),
|
|
459
|
-
link(trans.about.website, URLS.homepage),
|
|
460
|
-
link(trans.about.email, URLS.email),
|
|
461
|
-
'',
|
|
462
|
-
row(trans.about.license, `MIT | ${trans.about.author}: m1ngsama`),
|
|
463
|
-
].join('\n');
|
|
464
|
-
note(content, trans.about.title);
|
|
486
|
+
showAbout();
|
|
465
487
|
return;
|
|
466
488
|
}
|
|
467
|
-
// URL actions: repair, website, github, roadmap
|
|
468
489
|
const mappedUrl = URL_ACTIONS[action];
|
|
469
490
|
if (mappedUrl) {
|
|
470
491
|
if (flags.has('--open')) {
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
⠀⠀⠀⠀⠀⠀⠀⠙⠳⢤⣀⠂⠱⠄⢄⠈⣍⢍⣭⢩⢁⣤⠢⠞⠀⣉⡤⠞⠋⠀⠀⠀⠀⠀⠀⠀
|
|
18
|
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠒⠶⠤⣤⣤⣥⣬⣤⣤⠤⠶⠒⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
1
|
+
⢀⣠⡤⠶⠚⢚⣋⢋⣙⠛⡛⠓⠦⢤⣄⡀
|
|
2
|
+
⢀⣤⠞⡋⠁⠘⢤⣑⣩⡥⡭⠭⢥⣙⣑⠁⠴⣊⡙⠳⣤⡀
|
|
3
|
+
⢀⡴⢋⣡⡓⣙⡴⠚⠉⠁ ⠈⠉⠓⠮⣙⢓⣄⡙⢦⡀
|
|
4
|
+
⣠⠞⣰⡄⣡⠚⠁ ⠈⠓⣍⢪⢌⠻⣄
|
|
5
|
+
⢰⠋⢬⢊⣼⠥⠴⠒⠒⠢⠤⡀ ⢀⣀⠤⠤⠒⠢⠤⣄⡈⢳⡱⣡⠙⡆
|
|
6
|
+
⢠⡏⣸⣧⣾⣅ ⢀⣀⣀⣀⡀⠈⠱⡄ ⣰⣮⣄ ⢀⢀⣀⡀ ⠉⢢⣳⡨⠃⢹⡄
|
|
7
|
+
⣼⢰⡤⢿⣿⣿⠞⠉⠁ ⠈⠉⠲⣀⡹⠐⣿⣿⡿⠚⠉⠁⠈⠈⠑⠦⣀⡏⣇⠳⠌⣧
|
|
8
|
+
⡟⢘⡅⡟⠈ ⠁ ⠈ ⢻⠰⡖⢻
|
|
9
|
+
⣧⠨⣁⣧ ⣼⠸⢛⣼
|
|
10
|
+
⢻⠘⢶⢻⡀⢇ ⢸⠁⢀⡏⠈⠉⡟
|
|
11
|
+
⠘⣇⠵⢇⢧⠘⣄ ⢠⠇⢀⡼⢩⡕⣹⠃
|
|
12
|
+
⠸⣆⠹⢌⢣⡈⢢⡀ ⢀⡰⠋⢀⡼⣑⠦⣠⠇
|
|
13
|
+
⠙⢦⡓⢴⡙⢤⡉⠲⣄⡀ ⢀⣀⠴⠋⢀⡤⢫⡌⠋⡴⠋
|
|
14
|
+
⠈⠳⣌⡙⢁⡍⡲⢤⣉⡙⠓⠒⠲⠖⠒⠋⢉⣁⡤⢖⡩⣠⠙⣡⠞⠁
|
|
15
|
+
⠈⠛⢦⣌⡀⠲⢜⠭⡝⢓⣒⣚⢚⣍⡭⡰⠆⠙⣨⡴⠛⠁
|
|
16
|
+
⠈⠙⠓⠲⢥⣥⣅⣉⣩⣤⣤⡬⠖⠚⠋⠁
|
package/dist/main.js
CHANGED
|
@@ -1,52 +1,11 @@
|
|
|
1
1
|
import { runStartup } from './core/logo.js';
|
|
2
|
-
import { clearScreen
|
|
3
|
-
import { showMainMenu } from './core/menu.js';
|
|
4
|
-
import { c } from './core/theme.js';
|
|
2
|
+
import { clearScreen } from './core/ui.js';
|
|
5
3
|
import { enableVimKeys } from './core/vim-keys.js';
|
|
6
|
-
import { checkForUpdate } from './features/update.js';
|
|
7
|
-
import { showEventsPreview } from './features/calendar.js';
|
|
8
4
|
export async function main(options = {}) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
await runStartup();
|
|
16
|
-
}
|
|
17
|
-
// Fire update check in background; events fetch provides natural wait time.
|
|
18
|
-
// Its own AbortController is tied to whichever path finishes first, so a
|
|
19
|
-
// still-in-flight request never keeps the process alive after we're done
|
|
20
|
-
// with (or have stopped caring about) its result.
|
|
21
|
-
const updateAbort = new AbortController();
|
|
22
|
-
const updatePromise = checkForUpdate(updateAbort.signal);
|
|
23
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
24
|
-
// Launch app shell for interactive terminals
|
|
25
|
-
const { runApp } = await import('./app/app.js');
|
|
26
|
-
await runApp();
|
|
27
|
-
updateAbort.abort();
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
// Classic path for non-TTY (CI, pipes, redirects)
|
|
31
|
-
await showEventsPreview();
|
|
32
|
-
try {
|
|
33
|
-
const line = (await import('./features/schedule-view.js')).peekNextClassLine();
|
|
34
|
-
if (line)
|
|
35
|
-
console.log(line);
|
|
36
|
-
}
|
|
37
|
-
catch { /* best effort */ }
|
|
38
|
-
// Update check is very likely done by now; give it a short window if not
|
|
39
|
-
const updateMsg = await Promise.race([
|
|
40
|
-
updatePromise,
|
|
41
|
-
new Promise(r => setTimeout(r, 100, null)),
|
|
42
|
-
]);
|
|
43
|
-
updateAbort.abort();
|
|
44
|
-
if (updateMsg)
|
|
45
|
-
console.log(c.warn(updateMsg));
|
|
46
|
-
await showMainMenu();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
catch (err) {
|
|
50
|
-
handleGracefulExit(err);
|
|
51
|
-
}
|
|
5
|
+
enableVimKeys();
|
|
6
|
+
clearScreen();
|
|
7
|
+
if (!options.skipLogo)
|
|
8
|
+
await runStartup();
|
|
9
|
+
const { runApp } = await import('./app/app.js');
|
|
10
|
+
await runApp();
|
|
52
11
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nbtca/prompt",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"nbtca": "
|
|
11
|
-
"nbtca-welcome": "
|
|
10
|
+
"nbtca": "dist/index.js",
|
|
11
|
+
"nbtca-welcome": "dist/index.js"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
|
-
"bin/nbtca-welcome.js",
|
|
16
15
|
"README.md",
|
|
17
16
|
"SECURITY.md",
|
|
18
17
|
"LICENSE"
|
|
@@ -23,24 +22,31 @@
|
|
|
23
22
|
"dev:watch": "tsx watch src/index.ts",
|
|
24
23
|
"build": "tsc",
|
|
25
24
|
"postbuild": "node scripts/copy-assets.js",
|
|
26
|
-
"clean": "
|
|
25
|
+
"clean": "node scripts/clean.js",
|
|
27
26
|
"prebuild": "npm run clean",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
27
|
+
"format": "prettier --write .",
|
|
28
|
+
"format:check": "prettier --check .",
|
|
29
|
+
"lint": "eslint . --max-warnings=0",
|
|
30
|
+
"typecheck": "tsc --project tsconfig.typecheck.json",
|
|
31
|
+
"test": "vitest run && npm run test:cli",
|
|
32
|
+
"test:cli": "npm run build && bash scripts/test-cli.sh",
|
|
33
|
+
"check:package": "node scripts/check-package.mjs",
|
|
34
|
+
"check": "npm run format:check && npm run lint && npm run typecheck && npm test && npm run check:package && npm audit --audit-level=moderate",
|
|
35
|
+
"prepublishOnly": "npm run check"
|
|
30
36
|
},
|
|
31
37
|
"keywords": [
|
|
32
38
|
"cli",
|
|
33
39
|
"terminal",
|
|
34
40
|
"welcome",
|
|
35
41
|
"nbtca",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
42
|
+
"community",
|
|
43
|
+
"calendar",
|
|
38
44
|
"tui",
|
|
39
45
|
"interactive"
|
|
40
46
|
],
|
|
41
47
|
"dependencies": {
|
|
42
|
-
"@nbtca/docs": "^0.
|
|
43
|
-
"@nbtca/nbtcal": "^0.
|
|
48
|
+
"@nbtca/docs": "^0.3.0",
|
|
49
|
+
"@nbtca/nbtcal": "^0.4.0",
|
|
44
50
|
"chalk": "^5.6.2",
|
|
45
51
|
"cheerio": "1.0.0",
|
|
46
52
|
"fetch-cookie": "^3.2.0",
|
|
@@ -51,10 +57,16 @@
|
|
|
51
57
|
"tough-cookie": "^6.0.2"
|
|
52
58
|
},
|
|
53
59
|
"devDependencies": {
|
|
54
|
-
"@
|
|
55
|
-
"@types/node": "
|
|
56
|
-
"
|
|
60
|
+
"@eslint/js": "^9.39.5",
|
|
61
|
+
"@types/node": "20.12.12",
|
|
62
|
+
"eslint": "^9.39.5",
|
|
63
|
+
"eslint-config-prettier": "^10.1.8",
|
|
64
|
+
"globals": "^17.9.0",
|
|
65
|
+
"prettier": "^3.9.6",
|
|
66
|
+
"tsx": "^4.23.5",
|
|
57
67
|
"typescript": "^5.9.3",
|
|
68
|
+
"typescript-eslint": "^8.67.0",
|
|
69
|
+
"vite": "^6.4.3",
|
|
58
70
|
"vitest": "^3.2.7"
|
|
59
71
|
},
|
|
60
72
|
"engines": {
|
|
@@ -67,11 +79,11 @@
|
|
|
67
79
|
},
|
|
68
80
|
"repository": {
|
|
69
81
|
"type": "git",
|
|
70
|
-
"url": "git+https://github.com/nbtca/
|
|
82
|
+
"url": "git+https://github.com/nbtca/Prompt.git"
|
|
71
83
|
},
|
|
72
84
|
"bugs": {
|
|
73
|
-
"url": "https://github.com/nbtca/
|
|
85
|
+
"url": "https://github.com/nbtca/Prompt/issues"
|
|
74
86
|
},
|
|
75
|
-
"homepage": "https://github.com/nbtca/
|
|
76
|
-
"description": "
|
|
87
|
+
"homepage": "https://github.com/nbtca/Prompt#readme",
|
|
88
|
+
"description": "Terminal information system for the NBTCA community"
|
|
77
89
|
}
|
package/bin/nbtca-welcome.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { glyph, type, space } from '../theme.js';
|
|
2
|
-
export function screenWidth() {
|
|
3
|
-
return Math.min(process.stdout.columns || 80, 64);
|
|
4
|
-
}
|
|
5
|
-
export function renderScreen(opts) {
|
|
6
|
-
const width = opts.width ?? screenWidth();
|
|
7
|
-
const lines = [];
|
|
8
|
-
if (opts.title) {
|
|
9
|
-
lines.push(space.indent + type.heading(opts.title));
|
|
10
|
-
lines.push(space.indent + type.hint(glyph.rule().repeat(width)));
|
|
11
|
-
}
|
|
12
|
-
lines.push(opts.body);
|
|
13
|
-
if (opts.footer) {
|
|
14
|
-
lines.push('');
|
|
15
|
-
lines.push(space.indent + type.hint(opts.footer));
|
|
16
|
-
}
|
|
17
|
-
return lines.join('\n');
|
|
18
|
-
}
|
package/dist/core/menu.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimalist menu system
|
|
3
|
-
*/
|
|
4
|
-
import { runMenu } from './components/menu.js';
|
|
5
|
-
import { type, space, glyph } from './theme.js';
|
|
6
|
-
import { clearScreen } from './ui.js';
|
|
7
|
-
import { showCalendar } from '../features/calendar.js';
|
|
8
|
-
import { showDocsMenu } from '../features/docs.js';
|
|
9
|
-
import { showServiceStatus } from '../features/status.js';
|
|
10
|
-
import { showLinksMenu } from '../features/links.js';
|
|
11
|
-
import { showSettingsMenu } from '../features/settings.js';
|
|
12
|
-
import { showStudentTimetableMenu } from '../features/student-timetable.js';
|
|
13
|
-
import { showSchedule } from '../features/schedule-view.js';
|
|
14
|
-
import { t } from '../i18n/index.js';
|
|
15
|
-
function getMainMenuOptions() {
|
|
16
|
-
const trans = t();
|
|
17
|
-
return [
|
|
18
|
-
{ value: 'events', label: trans.menu.events, hint: trans.menu.eventsDesc || undefined },
|
|
19
|
-
{ value: 'schedule', label: t().timetable.menuEntry },
|
|
20
|
-
{ value: 'timetable', label: trans.menu.timetable, hint: trans.menu.timetableDesc || undefined },
|
|
21
|
-
{ value: 'docs', label: trans.menu.docs, hint: trans.menu.docsDesc || undefined },
|
|
22
|
-
{ value: 'status', label: trans.menu.status, hint: trans.menu.statusDesc || undefined },
|
|
23
|
-
{ value: 'links', label: trans.menu.links, hint: trans.menu.linksDesc || undefined },
|
|
24
|
-
{ value: 'settings', label: trans.menu.settings, hint: trans.menu.settingsDesc || undefined },
|
|
25
|
-
];
|
|
26
|
-
}
|
|
27
|
-
export async function showMainMenu() {
|
|
28
|
-
let first = true;
|
|
29
|
-
while (true) {
|
|
30
|
-
if (!first)
|
|
31
|
-
clearScreen();
|
|
32
|
-
first = false;
|
|
33
|
-
const trans = t();
|
|
34
|
-
const footer = `${glyph.updown()} ${trans.menu.hintMove} ${glyph.enter()} ${trans.menu.hintOpen} q ${trans.menu.hintQuit}`;
|
|
35
|
-
const action = await runMenu({
|
|
36
|
-
title: trans.menu.chooseAction,
|
|
37
|
-
options: getMainMenuOptions(),
|
|
38
|
-
footer,
|
|
39
|
-
});
|
|
40
|
-
if (action === null) {
|
|
41
|
-
console.log(space.indent + type.hint(t().common.goodbye));
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
await runMenuAction(action);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
export async function runMenuAction(action) {
|
|
48
|
-
switch (action) {
|
|
49
|
-
case 'events':
|
|
50
|
-
await showCalendar();
|
|
51
|
-
break;
|
|
52
|
-
case 'schedule':
|
|
53
|
-
await showSchedule();
|
|
54
|
-
break;
|
|
55
|
-
case 'timetable':
|
|
56
|
-
await showStudentTimetableMenu();
|
|
57
|
-
break;
|
|
58
|
-
case 'docs':
|
|
59
|
-
await showDocsMenu();
|
|
60
|
-
break;
|
|
61
|
-
case 'status':
|
|
62
|
-
await showServiceStatus();
|
|
63
|
-
break;
|
|
64
|
-
case 'links':
|
|
65
|
-
await showLinksMenu();
|
|
66
|
-
break;
|
|
67
|
-
case 'settings':
|
|
68
|
-
await showSettingsMenu();
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
package/dist/features/links.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Links — open NBTCA resources in browser
|
|
3
|
-
*/
|
|
4
|
-
import open from 'open';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import { runMenu, menuFooter } from '../core/components/menu.js';
|
|
7
|
-
import { createSpinner } from '../core/ui.js';
|
|
8
|
-
import { URLS } from '../config/data.js';
|
|
9
|
-
import { t } from '../i18n/index.js';
|
|
10
|
-
import { enterScreen, breadcrumb } from '../core/transitions.js';
|
|
11
|
-
async function openUrl(url) {
|
|
12
|
-
const trans = t();
|
|
13
|
-
const s = createSpinner(trans.links.opening);
|
|
14
|
-
try {
|
|
15
|
-
await open(url);
|
|
16
|
-
s.stop(trans.links.opened);
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
s.error(trans.links.error);
|
|
20
|
-
console.log(chalk.dim(` ${url}`));
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
export async function showLinksMenu() {
|
|
24
|
-
const trans = t();
|
|
25
|
-
await enterScreen(breadcrumb(trans.menu.links));
|
|
26
|
-
const selected = await runMenu({
|
|
27
|
-
title: trans.links.choose,
|
|
28
|
-
options: [
|
|
29
|
-
{ value: URLS.homepage, label: trans.links.website },
|
|
30
|
-
{ value: URLS.github, label: trans.links.github },
|
|
31
|
-
{ value: URLS.roadmap, label: trans.links.roadmap },
|
|
32
|
-
{ value: URLS.repair, label: trans.links.repair },
|
|
33
|
-
],
|
|
34
|
-
footer: menuFooter(),
|
|
35
|
-
});
|
|
36
|
-
if (selected === null)
|
|
37
|
-
return;
|
|
38
|
-
await openUrl(selected);
|
|
39
|
-
}
|