@nbtca/prompt 1.0.26 → 1.1.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/dist/features/calendar-heatmap.js +169 -0
- package/dist/features/calendar.js +66 -57
- package/dist/features/docs.js +3 -5
- package/dist/i18n/locales/en.json +7 -1
- package/dist/i18n/locales/zh.json +7 -1
- package/dist/index.js +16 -2
- package/package.json +5 -5
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heatmap renderer for calendar activity.
|
|
3
|
+
* GitHub-contributions-style grid: 7 weekday rows (Mon..Sun), week columns.
|
|
4
|
+
*/
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { pickIcon } from '../core/icons.js';
|
|
7
|
+
import { t, getCurrentLanguage } from '../i18n/index.js';
|
|
8
|
+
/** Parse a 'YYYY-MM-DD' date string into a UTC proxy Date (host-timezone-independent). */
|
|
9
|
+
function parseBucketDate(date) {
|
|
10
|
+
const parts = date.split('-').map(Number);
|
|
11
|
+
const y = parts[0] ?? 0;
|
|
12
|
+
const m = parts[1] ?? 1;
|
|
13
|
+
const d = parts[2] ?? 1;
|
|
14
|
+
return new Date(Date.UTC(y, m - 1, d));
|
|
15
|
+
}
|
|
16
|
+
/** 0=Sun,1=Mon,...,6=Sat -> Mon-indexed 0..6 */
|
|
17
|
+
function utcDayToMonIndex(utcDay) {
|
|
18
|
+
return (utcDay + 6) % 7;
|
|
19
|
+
}
|
|
20
|
+
/** Map a count to a unicode intensity glyph (or ASCII fallback). */
|
|
21
|
+
function countToGlyph(count) {
|
|
22
|
+
if (count <= 0)
|
|
23
|
+
return pickIcon('·', ' ');
|
|
24
|
+
if (count === 1)
|
|
25
|
+
return pickIcon('░', '.');
|
|
26
|
+
if (count === 2)
|
|
27
|
+
return pickIcon('▒', ':');
|
|
28
|
+
if (count === 3)
|
|
29
|
+
return pickIcon('▓', '-');
|
|
30
|
+
return pickIcon('█', '=');
|
|
31
|
+
}
|
|
32
|
+
/** Apply a green color ramp based on count. Identity when count is 0. */
|
|
33
|
+
function applyColor(glyph, count, useColor) {
|
|
34
|
+
if (!useColor || count <= 0)
|
|
35
|
+
return glyph;
|
|
36
|
+
if (count === 1)
|
|
37
|
+
return chalk.green(glyph);
|
|
38
|
+
if (count === 2)
|
|
39
|
+
return chalk.green(glyph);
|
|
40
|
+
if (count === 3)
|
|
41
|
+
return chalk.greenBright(glyph);
|
|
42
|
+
return chalk.bold.greenBright(glyph);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Render a GitHub-contributions-style heatmap grid.
|
|
46
|
+
*
|
|
47
|
+
* @param buckets Dense daily buckets from nbtcal's .heatmap() call.
|
|
48
|
+
* @param today The "today" date (used to determine the end column).
|
|
49
|
+
* @param options Optional rendering options.
|
|
50
|
+
*/
|
|
51
|
+
export function renderHeatmap(buckets, today, options) {
|
|
52
|
+
const useColor = options?.color === true;
|
|
53
|
+
const trans = t();
|
|
54
|
+
// Build a lookup map: 'YYYY-MM-DD' -> count
|
|
55
|
+
const countByDate = new Map();
|
|
56
|
+
for (const b of buckets) {
|
|
57
|
+
countByDate.set(b.date, b.count);
|
|
58
|
+
}
|
|
59
|
+
// Determine the grid window.
|
|
60
|
+
// The grid ends at "today" (aligned to Mon-start week column).
|
|
61
|
+
// The grid starts 365 days before today.
|
|
62
|
+
const todayProxy = new Date(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()));
|
|
63
|
+
// Find the Monday that starts the week containing today
|
|
64
|
+
const todayMonIndex = utcDayToMonIndex(todayProxy.getUTCDay());
|
|
65
|
+
const gridEndMs = todayProxy.getTime() + (6 - todayMonIndex) * 86400000; // Sunday of today's week
|
|
66
|
+
// Grid start: 52 full weeks back from the start of today's week, plus today's partial week
|
|
67
|
+
// Total columns = 53 weeks
|
|
68
|
+
const numCols = 53;
|
|
69
|
+
const gridStartMs = gridEndMs - (numCols * 7 - 1) * 86400000;
|
|
70
|
+
const gridStart = new Date(gridStartMs);
|
|
71
|
+
const columns = [];
|
|
72
|
+
let cursor = new Date(gridStart.getTime());
|
|
73
|
+
for (let col = 0; col < numCols; col++) {
|
|
74
|
+
const column = [];
|
|
75
|
+
for (let row = 0; row < 7; row++) {
|
|
76
|
+
const cursorMonIdx = utcDayToMonIndex(cursor.getUTCDay());
|
|
77
|
+
if (cursorMonIdx === row) {
|
|
78
|
+
const y = cursor.getUTCFullYear();
|
|
79
|
+
const m = String(cursor.getUTCMonth() + 1).padStart(2, '0');
|
|
80
|
+
const d = String(cursor.getUTCDate()).padStart(2, '0');
|
|
81
|
+
const dateStr = `${y}-${m}-${d}`;
|
|
82
|
+
const count = countByDate.get(dateStr) ?? 0;
|
|
83
|
+
column.push({ date: dateStr, count });
|
|
84
|
+
cursor = new Date(cursor.getTime() + 86400000);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
column.push(null);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
columns.push(column);
|
|
91
|
+
}
|
|
92
|
+
// Month labels row. Labels are 3-letter English abbreviations (universal and
|
|
93
|
+
// single-width, so alignment holds in any language). Each grid column occupies
|
|
94
|
+
// 2 terminal cells (glyph + joining space); we write each month's label into a
|
|
95
|
+
// character buffer starting at the column where that month begins, letting it
|
|
96
|
+
// overflow rightward into the spacing of the following columns (months are
|
|
97
|
+
// ~4-5 columns apart, so labels never collide).
|
|
98
|
+
const weekdayLabel = ' '; // 3-char prefix, matches the grid rows' "Mo " prefix
|
|
99
|
+
const monthFmt = new Intl.DateTimeFormat('en-US', { month: 'short', timeZone: 'UTC' });
|
|
100
|
+
const cellsWidth = numCols * 2;
|
|
101
|
+
const monthChars = new Array(cellsWidth).fill(' ');
|
|
102
|
+
let prevMonth = -1;
|
|
103
|
+
for (let col = 0; col < numCols; col++) {
|
|
104
|
+
let labelDate = null;
|
|
105
|
+
for (let row = 0; row < 7; row++) {
|
|
106
|
+
const cell = columns[col]?.[row];
|
|
107
|
+
if (cell !== null && cell !== undefined) {
|
|
108
|
+
labelDate = parseBucketDate(cell.date);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (labelDate === null)
|
|
113
|
+
continue;
|
|
114
|
+
const month = labelDate.getUTCMonth();
|
|
115
|
+
if (month !== prevMonth) {
|
|
116
|
+
prevMonth = month;
|
|
117
|
+
const label = monthFmt.format(labelDate); // e.g. "Jun"
|
|
118
|
+
const start = col * 2;
|
|
119
|
+
for (let i = 0; i < label.length && start + i < cellsWidth; i++) {
|
|
120
|
+
monthChars[start + i] = label[i] ?? ' ';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const monthLabelLine = weekdayLabel + monthChars.join('');
|
|
125
|
+
// Weekday labels (Mon/Wed/Fri only, index 0/2/4 in Mon-indexed scheme)
|
|
126
|
+
const lang = getCurrentLanguage();
|
|
127
|
+
const weekdayNames = lang === 'zh'
|
|
128
|
+
? ['一', ' ', '三', ' ', '五', ' ', ' ']
|
|
129
|
+
: ['Mo', ' ', 'We', ' ', 'Fr', ' ', ' '];
|
|
130
|
+
// Build output lines
|
|
131
|
+
const lines = [];
|
|
132
|
+
// Title line
|
|
133
|
+
lines.push(trans.calendar.heatmap.title);
|
|
134
|
+
lines.push('');
|
|
135
|
+
// Month labels line
|
|
136
|
+
lines.push(monthLabelLine);
|
|
137
|
+
// Grid rows (7 rows: Mon..Sun)
|
|
138
|
+
for (let row = 0; row < 7; row++) {
|
|
139
|
+
const wdLabel = weekdayNames[row] ?? ' ';
|
|
140
|
+
const cells = columns.map(col => {
|
|
141
|
+
const cell = col[row];
|
|
142
|
+
if (cell === null || cell === undefined)
|
|
143
|
+
return ' ';
|
|
144
|
+
const glyph = countToGlyph(cell.count);
|
|
145
|
+
return applyColor(glyph, cell.count, useColor);
|
|
146
|
+
});
|
|
147
|
+
lines.push(`${wdLabel} ${cells.join(' ')}`);
|
|
148
|
+
}
|
|
149
|
+
// Legend line
|
|
150
|
+
const legendGlyphs = [
|
|
151
|
+
pickIcon('·', ' '),
|
|
152
|
+
pickIcon('░', '.'),
|
|
153
|
+
pickIcon('▒', ':'),
|
|
154
|
+
pickIcon('▓', '-'),
|
|
155
|
+
pickIcon('█', '='),
|
|
156
|
+
];
|
|
157
|
+
const legendColored = useColor
|
|
158
|
+
? [
|
|
159
|
+
legendGlyphs[0] ?? '·',
|
|
160
|
+
applyColor(legendGlyphs[1] ?? '░', 1, true),
|
|
161
|
+
applyColor(legendGlyphs[2] ?? '▒', 2, true),
|
|
162
|
+
applyColor(legendGlyphs[3] ?? '▓', 3, true),
|
|
163
|
+
applyColor(legendGlyphs[4] ?? '█', 4, true),
|
|
164
|
+
]
|
|
165
|
+
: legendGlyphs;
|
|
166
|
+
lines.push('');
|
|
167
|
+
lines.push(`${trans.calendar.heatmap.legendLess} ${legendColored.join('')} ${trans.calendar.heatmap.legendMore}`);
|
|
168
|
+
return lines.join('\n');
|
|
169
|
+
}
|
|
@@ -1,60 +1,73 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Calendar module
|
|
3
3
|
* Fetches and renders upcoming events with Unicode box table.
|
|
4
|
+
* Data layer powered by @nbtca/nbtcal.
|
|
4
5
|
*/
|
|
5
|
-
import
|
|
6
|
+
import { loadCalendar, FeedFetchError, FeedParseError } from '@nbtca/nbtcal';
|
|
6
7
|
import chalk from 'chalk';
|
|
7
8
|
import { select, isCancel } from '@clack/prompts';
|
|
8
9
|
import { info, createSpinner } from '../core/ui.js';
|
|
9
10
|
import { pickIcon } from '../core/icons.js';
|
|
10
11
|
import { padEndV, truncate } from '../core/text.js';
|
|
11
12
|
import { t } from '../i18n/index.js';
|
|
12
|
-
import {
|
|
13
|
-
|
|
13
|
+
import { URLS } from '../config/data.js';
|
|
14
|
+
import { renderHeatmap } from './calendar-heatmap.js';
|
|
15
|
+
function formatDate(date) {
|
|
16
|
+
const now = new Date();
|
|
17
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
18
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
19
|
+
if (date.getFullYear() !== now.getFullYear()) {
|
|
20
|
+
return `${date.getFullYear()}-${month}-${day}`;
|
|
21
|
+
}
|
|
22
|
+
return `${month}-${day}`;
|
|
23
|
+
}
|
|
24
|
+
function formatTime(date) {
|
|
25
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
26
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
27
|
+
return `${hours}:${minutes}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load the calendar, wrapping errors with a localized message.
|
|
31
|
+
*/
|
|
32
|
+
async function loadCalendarOrThrow() {
|
|
14
33
|
try {
|
|
15
|
-
|
|
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}` },
|
|
20
|
-
});
|
|
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);
|
|
26
|
-
const comp = new ICAL.Component(jcalData);
|
|
27
|
-
const vevents = comp.getAllSubcomponents('vevent');
|
|
28
|
-
const events = [];
|
|
29
|
-
const now = new Date();
|
|
30
|
-
const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
|
|
31
|
-
for (const vevent of vevents) {
|
|
32
|
-
const event = new ICAL.Event(vevent);
|
|
33
|
-
const startDate = event.startDate.toJSDate();
|
|
34
|
-
if (startDate >= now && startDate <= thirtyDaysLater) {
|
|
35
|
-
const trans = t();
|
|
36
|
-
const untitledEvent = trans.calendar.untitledEvent;
|
|
37
|
-
const tbdLocation = trans.calendar.tbdLocation;
|
|
38
|
-
events.push({
|
|
39
|
-
date: formatDate(startDate),
|
|
40
|
-
time: formatTime(startDate),
|
|
41
|
-
title: event.summary || untitledEvent,
|
|
42
|
-
location: event.location || tbdLocation,
|
|
43
|
-
description: event.description || '',
|
|
44
|
-
startDate
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
events.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
|
49
|
-
return events;
|
|
34
|
+
return await loadCalendar({ timeoutMs: 15000 });
|
|
50
35
|
}
|
|
51
36
|
catch (err) {
|
|
52
|
-
const detail = err instanceof
|
|
53
|
-
?
|
|
37
|
+
const detail = err instanceof FeedFetchError || err instanceof FeedParseError
|
|
38
|
+
? err.message
|
|
54
39
|
: String(err);
|
|
55
40
|
throw new Error(`${t().calendar.error}: ${detail}`);
|
|
56
41
|
}
|
|
57
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Map a nbtcal CalendarEvent to prompt's Event type.
|
|
45
|
+
*/
|
|
46
|
+
export function toDisplayEvent(e) {
|
|
47
|
+
const trans = t();
|
|
48
|
+
return {
|
|
49
|
+
date: formatDate(e.start),
|
|
50
|
+
time: e.isAllDay ? '' : formatTime(e.start),
|
|
51
|
+
title: e.title ?? trans.calendar.untitledEvent,
|
|
52
|
+
location: e.location ?? trans.calendar.tbdLocation,
|
|
53
|
+
description: e.description ?? '',
|
|
54
|
+
startDate: e.start,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Fetch upcoming events (next 30 days), including recurring occurrences.
|
|
59
|
+
*/
|
|
60
|
+
export async function fetchEvents() {
|
|
61
|
+
return (await loadCalendarOrThrow()).upcoming({ days: 30 }).map(toDisplayEvent);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Fetch trailing-year heatmap buckets.
|
|
65
|
+
*/
|
|
66
|
+
export async function fetchHeatmapBuckets() {
|
|
67
|
+
const now = new Date();
|
|
68
|
+
const start = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000);
|
|
69
|
+
return (await loadCalendarOrThrow()).heatmap({ start, end: now, bucket: 'day' });
|
|
70
|
+
}
|
|
58
71
|
export function serializeEvents(events) {
|
|
59
72
|
return events.map((event) => ({
|
|
60
73
|
date: event.date,
|
|
@@ -62,23 +75,9 @@ export function serializeEvents(events) {
|
|
|
62
75
|
title: event.title,
|
|
63
76
|
location: event.location,
|
|
64
77
|
description: event.description,
|
|
65
|
-
startDateISO: event.startDate.toISOString()
|
|
78
|
+
startDateISO: event.startDate.toISOString(),
|
|
66
79
|
}));
|
|
67
80
|
}
|
|
68
|
-
function formatDate(date) {
|
|
69
|
-
const now = new Date();
|
|
70
|
-
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
71
|
-
const day = String(date.getDate()).padStart(2, '0');
|
|
72
|
-
if (date.getFullYear() !== now.getFullYear()) {
|
|
73
|
-
return `${date.getFullYear()}-${month}-${day}`;
|
|
74
|
-
}
|
|
75
|
-
return `${month}-${day}`;
|
|
76
|
-
}
|
|
77
|
-
function formatTime(date) {
|
|
78
|
-
const hours = String(date.getHours()).padStart(2, '0');
|
|
79
|
-
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
80
|
-
return `${hours}:${minutes}`;
|
|
81
|
-
}
|
|
82
81
|
/**
|
|
83
82
|
* Render events as a Unicode box-drawing table
|
|
84
83
|
*/
|
|
@@ -153,8 +152,18 @@ export async function showCalendar() {
|
|
|
153
152
|
const trans = t();
|
|
154
153
|
const s = createSpinner(trans.calendar.loading);
|
|
155
154
|
try {
|
|
156
|
-
const
|
|
155
|
+
const cal = await loadCalendarOrThrow();
|
|
156
|
+
const events = cal.upcoming({ days: 30 }).map(toDisplayEvent);
|
|
157
157
|
s.stop(`${events.length} ${trans.calendar.eventsFound}`);
|
|
158
|
+
// Render heatmap header
|
|
159
|
+
const now = new Date();
|
|
160
|
+
const heatmapBuckets = cal.heatmap({
|
|
161
|
+
start: new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000),
|
|
162
|
+
end: now,
|
|
163
|
+
bucket: 'day',
|
|
164
|
+
});
|
|
165
|
+
console.log();
|
|
166
|
+
console.log(renderHeatmap(heatmapBuckets, now, { color: true }));
|
|
158
167
|
displayEvents(events);
|
|
159
168
|
if (events.length > 0) {
|
|
160
169
|
const options = [
|
package/dist/features/docs.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 获取并渲染Markdown文档
|
|
4
4
|
*/
|
|
5
5
|
import { marked } from 'marked';
|
|
6
|
-
import
|
|
6
|
+
import { markedTerminal } from 'marked-terminal';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import open from 'open';
|
|
9
9
|
import { select, isCancel, confirm, text } from '@clack/prompts';
|
|
@@ -56,9 +56,7 @@ function ensureMarkedConfigured() {
|
|
|
56
56
|
if (_markedConfigured)
|
|
57
57
|
return;
|
|
58
58
|
_markedConfigured = true;
|
|
59
|
-
marked.use(
|
|
60
|
-
renderer: new TerminalRenderer(getRendererOptions(getTerminalType()))
|
|
61
|
-
});
|
|
59
|
+
marked.use(markedTerminal(getRendererOptions(getTerminalType())));
|
|
62
60
|
}
|
|
63
61
|
// ─── marked-terminal renderer ─────────────────────────────────────────────────
|
|
64
62
|
function getRendererOptions(type) {
|
|
@@ -293,7 +291,7 @@ function cleanMarkdownContent(content, type = getTerminalType()) {
|
|
|
293
291
|
if (type === 'basic') {
|
|
294
292
|
c = c.replace(/!\[([^\]]*)\]\([^)]+\)/g, (_, alt) => `${pickIcon('📎', '[image]')} ${alt || 'image'}`);
|
|
295
293
|
}
|
|
296
|
-
else
|
|
294
|
+
else {
|
|
297
295
|
c = c.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, url) => {
|
|
298
296
|
const filename = url.split('/').pop() || url;
|
|
299
297
|
return `${pickIcon('🖼️', '[image]')} **${alt || 'image'}** _(${filename})_`;
|
|
@@ -48,7 +48,12 @@
|
|
|
48
48
|
"tbdLocation": "TBD",
|
|
49
49
|
"subscribeHint": "Subscribe to calendar",
|
|
50
50
|
"viewDetail": "Select an event for details:",
|
|
51
|
-
"noDescription": "No additional details"
|
|
51
|
+
"noDescription": "No additional details",
|
|
52
|
+
"heatmap": {
|
|
53
|
+
"title": "Activity (last 12 months)",
|
|
54
|
+
"legendLess": "Less",
|
|
55
|
+
"legendMore": "More"
|
|
56
|
+
}
|
|
52
57
|
},
|
|
53
58
|
"docs": {
|
|
54
59
|
"loading": "Loading documentation list...",
|
|
@@ -187,6 +192,7 @@
|
|
|
187
192
|
"flagInterval": "Refresh interval (status --watch)",
|
|
188
193
|
"flagTimeout": "HTTP timeout (status)",
|
|
189
194
|
"flagRetries": "Retry count (status)",
|
|
195
|
+
"flagHeatmap": "Activity heatmap (events)",
|
|
190
196
|
"flagPlain": "Disable colors",
|
|
191
197
|
"flagNoLogo": "Skip logo",
|
|
192
198
|
"unknownCommand": "Unknown command: {command}",
|
|
@@ -48,7 +48,12 @@
|
|
|
48
48
|
"tbdLocation": "待定",
|
|
49
49
|
"subscribeHint": "订阅日历",
|
|
50
50
|
"viewDetail": "选择活动查看详情:",
|
|
51
|
-
"noDescription": "暂无详细信息"
|
|
51
|
+
"noDescription": "暂无详细信息",
|
|
52
|
+
"heatmap": {
|
|
53
|
+
"title": "近一年活跃度",
|
|
54
|
+
"legendLess": "少",
|
|
55
|
+
"legendMore": "多"
|
|
56
|
+
}
|
|
52
57
|
},
|
|
53
58
|
"docs": {
|
|
54
59
|
"loading": "正在加载文档列表...",
|
|
@@ -187,6 +192,7 @@
|
|
|
187
192
|
"flagInterval": "刷新间隔(status --watch)",
|
|
188
193
|
"flagTimeout": "HTTP 超时时间(status)",
|
|
189
194
|
"flagRetries": "重试次数(status)",
|
|
195
|
+
"flagHeatmap": "活动热力图 (events)",
|
|
190
196
|
"flagPlain": "禁用颜色",
|
|
191
197
|
"flagNoLogo": "跳过 Logo",
|
|
192
198
|
"unknownCommand": "未知命令: {command}",
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import open from 'open';
|
|
6
6
|
import { main } from './main.js';
|
|
7
|
-
import { fetchEvents, renderEventsTable, serializeEvents } from './features/calendar.js';
|
|
7
|
+
import { fetchEvents, fetchHeatmapBuckets, renderEventsTable, serializeEvents } from './features/calendar.js';
|
|
8
|
+
import { renderHeatmap } from './features/calendar-heatmap.js';
|
|
8
9
|
import { checkServices, countServiceHealth, hasServiceFailures, renderServiceStatusTable, serializeServiceStatus } from './features/status.js';
|
|
9
10
|
import { pickIcon } from './core/icons.js';
|
|
10
11
|
import { applyColorModePreference } from './config/preferences.js';
|
|
@@ -35,7 +36,7 @@ const URL_ACTIONS = {
|
|
|
35
36
|
github: URLS.github,
|
|
36
37
|
roadmap: URLS.roadmap
|
|
37
38
|
};
|
|
38
|
-
const KNOWN_FLAGS = new Set(['--help', '--version', '--open', '--json', '--plain', '--no-logo', '--watch', '--today']);
|
|
39
|
+
const KNOWN_FLAGS = new Set(['--help', '--version', '--open', '--json', '--plain', '--no-logo', '--watch', '--today', '--heatmap']);
|
|
39
40
|
const KNOWN_FLAG_PREFIXES = ['--interval=', '--timeout=', '--retries=', '--next='];
|
|
40
41
|
const STATUS_WATCH_INTERVAL_MIN = 3;
|
|
41
42
|
const STATUS_WATCH_INTERVAL_MAX = 300;
|
|
@@ -81,6 +82,7 @@ function getAllowedFlagsFor(command) {
|
|
|
81
82
|
case 'events':
|
|
82
83
|
allowed.add('--json');
|
|
83
84
|
allowed.add('--today');
|
|
85
|
+
allowed.add('--heatmap');
|
|
84
86
|
return allowed;
|
|
85
87
|
case 'status':
|
|
86
88
|
allowed.add('--json');
|
|
@@ -160,6 +162,7 @@ function printHelp() {
|
|
|
160
162
|
console.log(` --help ${c.flagHelp}`);
|
|
161
163
|
console.log(` --open ${c.flagOpen}`);
|
|
162
164
|
console.log(` --json ${c.flagJson}`);
|
|
165
|
+
console.log(` --heatmap ${c.flagHeatmap}`);
|
|
163
166
|
console.log(` --today ${c.flagToday}`);
|
|
164
167
|
console.log(` --next=<n> ${c.flagNext}`);
|
|
165
168
|
console.log(` --watch ${c.flagWatch}`);
|
|
@@ -170,6 +173,17 @@ function printHelp() {
|
|
|
170
173
|
console.log(` --no-logo ${c.flagNoLogo}`);
|
|
171
174
|
}
|
|
172
175
|
async function runEventsCommand(flags) {
|
|
176
|
+
if (flags.has('--heatmap')) {
|
|
177
|
+
const buckets = await fetchHeatmapBuckets();
|
|
178
|
+
if (flags.has('--json')) {
|
|
179
|
+
process.stdout.write(JSON.stringify(buckets, null, 2) + '\n');
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
const useColor = !flags.has('--plain') && !!process.stdout.isTTY;
|
|
183
|
+
console.log(renderHeatmap(buckets, new Date(), { color: useColor }));
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
173
187
|
let events = await fetchEvents();
|
|
174
188
|
if (flags.has('--today')) {
|
|
175
189
|
const now = new Date();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nbtca/prompt",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"clean": "rm -rf dist",
|
|
26
26
|
"prebuild": "npm run clean",
|
|
27
27
|
"prepublishOnly": "npm run build",
|
|
28
|
-
"test": "npm run build && bash scripts/test-cli.sh"
|
|
28
|
+
"test": "npm run build && vitest run && bash scripts/test-cli.sh"
|
|
29
29
|
},
|
|
30
30
|
"keywords": [
|
|
31
31
|
"cli",
|
|
@@ -39,19 +39,19 @@
|
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@clack/prompts": "^1.2.0",
|
|
42
|
+
"@nbtca/nbtcal": "^0.2.1",
|
|
42
43
|
"chalk": "^5.6.2",
|
|
43
44
|
"gradient-string": "^3.0.0",
|
|
44
|
-
"ical.js": "^2.2.1",
|
|
45
45
|
"marked": "^15.0.12",
|
|
46
46
|
"marked-terminal": "^7.0.0",
|
|
47
47
|
"open": "^11.0.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/gradient-string": "^1.1.6",
|
|
51
|
-
"@types/marked-terminal": "^3.1.3",
|
|
52
51
|
"@types/node": "^22.19.17",
|
|
53
52
|
"tsx": "^4.21.0",
|
|
54
|
-
"typescript": "^5.9.3"
|
|
53
|
+
"typescript": "^5.9.3",
|
|
54
|
+
"vitest": "^3.2.4"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
|
57
57
|
"node": ">=20.12.0"
|