@nbtca/prompt 1.0.27 → 1.1.1

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/core/logo.js CHANGED
@@ -1,70 +1,47 @@
1
1
  /**
2
- * Smart logo display module
3
- * Attempts to display iTerm2 image format logo, falls back to ASCII art
2
+ * Startup logo: a high-precision braille dot-matrix render of the NBTCA emblem
3
+ * (generated from CA-logo.svg), shown with the brand blue->cyan gradient.
4
+ * Falls back to plain ASCII on terminals without Unicode/braille support.
4
5
  */
5
6
  import { readFileSync } from 'fs';
6
7
  import { fileURLToPath } from 'url';
7
8
  import { dirname, join } from 'path';
8
9
  import gradient from 'gradient-string';
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = dirname(__filename);
11
- /**
12
- * Create blue-toned gradient effect
13
- */
14
- function createBlueGradient(text) {
15
- const blueGradient = gradient([
16
- { color: '#1e3a8a', pos: 0 }, // Deep blue
17
- { color: '#0ea5e9', pos: 0.5 }, // Sky blue
18
- { color: '#06b6d4', pos: 1 } // Cyan
19
- ]);
20
- return blueGradient(text);
21
- }
22
- /**
23
- * Display description text (instant, no animation)
24
- */
25
- function printDescription() {
26
- const tagline = 'To be at the intersection of technology and liberal arts.';
27
- console.log();
28
- if (process.env['NO_COLOR']) {
29
- console.log(tagline);
10
+ import { useUnicodeIcons } from './icons.js';
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const TAGLINE = 'To be at the intersection of technology and liberal arts.';
13
+ // Brand gradient: emblem blue -> sky -> cyan.
14
+ const brand = gradient([
15
+ { color: '#124689', pos: 0 },
16
+ { color: '#0ea5e9', pos: 0.55 },
17
+ { color: '#06b6d4', pos: 1 },
18
+ ]);
19
+ function readArt(file) {
20
+ try {
21
+ return readFileSync(join(__dirname, '../logo', file), 'utf-8').replace(/\s+$/, '');
30
22
  }
31
- else {
32
- console.log(createBlueGradient(tagline));
23
+ catch {
24
+ return null;
33
25
  }
34
- console.log();
35
26
  }
36
- /**
37
- * Attempt to read and display logo file
38
- */
27
+ function paint(text, color) {
28
+ if (!color)
29
+ return text;
30
+ // multiline keeps the gradient aligned down the whole block; fall back to a
31
+ // per-line gradient if the installed gradient-string lacks .multiline.
32
+ const fn = brand;
33
+ return typeof fn.multiline === 'function'
34
+ ? fn.multiline(text)
35
+ : text.split('\n').map((line) => brand(line)).join('\n');
36
+ }
39
37
  export function printLogo() {
40
- if (!process.stdout.isTTY) {
38
+ if (!process.stdout.isTTY)
41
39
  return;
42
- }
43
- try {
44
- const logoPath = join(__dirname, '../logo/logo.txt');
45
- const logoContent = readFileSync(logoPath, 'utf-8');
46
- if (logoContent && logoContent.length > 100) {
47
- console.log(logoContent);
48
- printDescription();
49
- return;
50
- }
51
- }
52
- catch {
53
- // iTerm2 logo read failed, continue trying ASCII logo
54
- }
55
- try {
56
- const asciiLogoPath = join(__dirname, '../logo/ascii-logo.txt');
57
- const asciiContent = readFileSync(asciiLogoPath, 'utf-8');
58
- console.log();
59
- const lines = asciiContent.split('\n').filter(line => line.trim());
60
- lines.forEach(line => {
61
- console.log(createBlueGradient(line));
62
- });
63
- printDescription();
64
- }
65
- catch {
66
- console.log();
67
- console.log(createBlueGradient(' NBTCA'));
68
- printDescription();
69
- }
40
+ const color = !process.env['NO_COLOR'];
41
+ const art = useUnicodeIcons() ? readArt('ca-dotmatrix.txt') : readArt('ascii-logo.txt');
42
+ console.log();
43
+ console.log(paint(art ?? 'NBTCA', color));
44
+ console.log();
45
+ console.log(color ? brand(TAGLINE) : TAGLINE);
46
+ console.log();
70
47
  }
@@ -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
- * ICS calendar module
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 ICAL from 'ical.js';
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 { APP_INFO, URLS } from '../config/data.js';
13
- export async function fetchEvents() {
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
- 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}` },
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 Error
53
- ? (err.name === 'AbortError' ? 'Request timed out' : err.message)
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 events = await fetchEvents();
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 = [
@@ -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();
@@ -0,0 +1,16 @@
1
+ ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⡶⢞⢛⣛⣛⣛⣛⣛⡳⠶⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀
2
+ ⠀⠀⠀⠀⠀⢀⣤⠾⡋⠁⠘⢽⣛⣿⣯⣿⣭⣯⣿⣛⡁⢴⣮⡝⠷⣤⡀⠀⠀⠀⠀⠀
3
+ ⠀⠀⠀⢀⣴⢟⣵⡿⣻⡵⠞⠋⠉⠀⠀⠀⠀⠀⠀⠉⠙⠳⢮⣟⢷⣎⡻⣦⡀⠀⠀⠀
4
+ ⠀⠀⣠⡟⣵⡦⣩⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣝⢿⣎⣻⣄⠀⠀
5
+ ⠀⣰⠏⣼⣎⣾⡥⠶⠶⠶⠶⢤⣀⠀⠀⠀⠀⠀⣠⡤⠶⠶⠶⠦⣄⡈⢷⣻⣯⠹⣆⠀
6
+ ⢰⣟⣾⣿⣾⣥⠀⢀⣀⣀⣀⡀⠈⠳⡄⠀⣴⣯⣅⠀⢀⣀⣀⣀⠀⠙⢮⣷⡹⣃⢻⡆
7
+ ⣾⢱⣬⣿⣿⣿⠟⠋⠉⠀⠈⠉⠳⣄⣿⠐⣿⣿⣿⠞⠉⠉⠉⠉⠙⢦⣀⡟⣧⠿⠬⣷
8
+ ⣿⢸⡅⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⢰⣶⣿
9
+ ⣿⢨⣁⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⢸⢿⣿
10
+ ⢿⡸⣿⢿⡄⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⠃⢠⡟⠈⢉⡿
11
+ ⠸⣧⢷⣏⢷⠘⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠏⠀⡾⣭⡷⣿⠇
12
+ ⠀⠹⣎⢽⣎⢷⡘⢧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠋⢀⡾⣵⡦⣰⠏⠀
13
+ ⠀⠀⠙⣧⡻⣷⣝⢦⡙⠲⣤⣀⠀⠀⠀⠀⠀⠀⠀⢀⣠⡴⠋⢁⡴⣫⣌⢛⣼⠋⠀⠀
14
+ ⠀⠀⠀⠈⠻⣮⡿⣡⣽⡳⢤⣍⡙⠓⠒⠶⠖⠒⠛⢉⣡⡤⣞⣫⣦⢙⣵⠟⠁⠀⠀⠀
15
+ ⠀⠀⠀⠀⠀⠈⠛⢷⣍⡁⠷⣞⣭⣟⣟⣟⣛⢛⣿⣭⣵⠆⢛⣩⡷⠛⠁⠀⠀⠀⠀⠀
16
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⠶⢯⣭⣭⣭⣭⣭⣭⡽⠶⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbtca/prompt",
3
- "version": "1.0.27",
3
+ "version": "1.1.1",
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,9 +39,9 @@
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"
@@ -50,7 +50,8 @@
50
50
  "@types/gradient-string": "^1.1.6",
51
51
  "@types/node": "^22.19.17",
52
52
  "tsx": "^4.21.0",
53
- "typescript": "^5.9.3"
53
+ "typescript": "^5.9.3",
54
+ "vitest": "^3.2.4"
54
55
  },
55
56
  "engines": {
56
57
  "node": ">=20.12.0"
@@ -1,12 +0,0 @@
1
- ⠀⠀⠀⠀⠀⠀⣀⠤⡐⣒⣈⡉⡉⠁⠒⠂⠤⣀⠀⠀⠀⠀⠀⠀
2
- ⠀⠀⠀⢀⠔⣋⠄⣀⠤⠜⠒⠒⠒⠒⠣⠤⣁⢤⢙⠢⡀⠀⠀⠀
3
- ⠀⠀⡴⣡⠈⡥⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢬⡠⡈⢦⠀⠀
4
- ⠀⡜⡀⢪⠮⠐⠒⠂⠠⢀⠀⠀⠀⡀⠄⠐⠒⠀⠄⡑⡔⢀⢣⠀
5
- ⠸⠀⢳⣷⣆⠠⠀⠀⠠⡀⠱⠀⣾⣶⣀⠤⠀⠠⢄⠈⠞⡈⡀⠇
6
- ⡇⠄⠸⠉⠁⠀⠀⠀⠀⠈⠈⠀⠉⠉⠀⠀⠀⠀⠀⠁⠀⠇⣠⢸
7
- ⡇⠍⢠⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⢋⢸
8
- ⢱⠀⠘⡄⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠇⢠⠃⠀⡎
9
- ⠀⢆⢺⠐⡌⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠎⢠⢢⡁⡰⠀
10
- ⠀⠈⠣⡂⢪⠢⡁⠢⢀⡀⠀⠀⠀⠀⣀⠠⠒⢁⠔⠅⢈⠜⠁⠀
11
- ⠀⠀⠀⠙⠢⡀⠴⠑⢂⠤⠉⢉⡉⠁⠤⠐⠊⠤⢅⠔⠋⠀⠀⠀
12
- ⠀⠀⠀⠀⠀⠈⠑⠢⠤⣐⣘⣒⣁⣒⣐⠤⠔⠊⠁⠀⠀⠀⠀⠀