error-ux-cli 1.0.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/lib/utils.js ADDED
@@ -0,0 +1,386 @@
1
+ const { exec } = require('child_process');
2
+ const fs = require('fs');
3
+ const os = require('os');
4
+ const path = require('path');
5
+
6
+ const FRAMES = ['|', '/', '-', '\\'];
7
+ const ANIMATION_INTERVAL_MS = 90;
8
+ const BOX = {
9
+ topLeft: '\u250c',
10
+ topRight: '\u2510',
11
+ bottomLeft: '\u2514',
12
+ bottomRight: '\u2518',
13
+ horizontal: '\u2500',
14
+ vertical: '\u2502',
15
+ leftJoin: '\u251c',
16
+ rightJoin: '\u2524',
17
+ topJoin: '\u252c',
18
+ bottomJoin: '\u2534',
19
+ cross: '\u253c'
20
+ };
21
+
22
+ function canAnimate() {
23
+ return Boolean(process.stdout.isTTY && !process.env.CI && process.env.NO_ANIMATION !== '1');
24
+ }
25
+
26
+ function sleep(ms) {
27
+ return new Promise(resolve => setTimeout(resolve, ms));
28
+ }
29
+
30
+ function clearLine() {
31
+ if (process.stdout.clearLine && process.stdout.cursorTo) {
32
+ process.stdout.clearLine(0);
33
+ process.stdout.cursorTo(0);
34
+ }
35
+ }
36
+
37
+ function writeAnimatedLine(message, frameIndex = 0) {
38
+ clearLine();
39
+ process.stdout.write(`${FRAMES[frameIndex % FRAMES.length]} ${message}`);
40
+ }
41
+
42
+ async function animateMessage(message, durationMs = 450, showWhenStatic = true) {
43
+ if (!canAnimate()) {
44
+ if (showWhenStatic) {
45
+ console.log(message);
46
+ }
47
+ return;
48
+ }
49
+
50
+ const startedAt = Date.now();
51
+ let frameIndex = 0;
52
+ while (Date.now() - startedAt < durationMs) {
53
+ writeAnimatedLine(message, frameIndex);
54
+ frameIndex++;
55
+ await sleep(ANIMATION_INTERVAL_MS);
56
+ }
57
+ clearLine();
58
+ }
59
+
60
+ async function withSpinner(message, task) {
61
+ if (!canAnimate()) {
62
+ return task();
63
+ }
64
+
65
+ let frameIndex = 0;
66
+ const timer = setInterval(() => {
67
+ writeAnimatedLine(message, frameIndex);
68
+ frameIndex++;
69
+ }, ANIMATION_INTERVAL_MS);
70
+
71
+ writeAnimatedLine(message, frameIndex);
72
+ frameIndex++;
73
+
74
+ try {
75
+ return await task();
76
+ } finally {
77
+ clearInterval(timer);
78
+ clearLine();
79
+ }
80
+ }
81
+
82
+ async function printAnimatedLines(lines, delayMs = 35) {
83
+ if (!canAnimate()) {
84
+ lines.forEach(line => console.log(line));
85
+ return;
86
+ }
87
+
88
+ for (const line of lines) {
89
+ console.log(line);
90
+ await sleep(delayMs);
91
+ }
92
+ }
93
+
94
+ async function typeAnimatedLines(lines, charDelayMs = 3, lineDelayMs = 35) {
95
+ if (!canAnimate()) {
96
+ lines.forEach(line => console.log(line));
97
+ return;
98
+ }
99
+
100
+ for (const line of lines) {
101
+ for (const char of line) {
102
+ process.stdout.write(char);
103
+ await sleep(charDelayMs);
104
+ }
105
+ process.stdout.write('\n');
106
+ await sleep(lineDelayMs);
107
+ }
108
+ }
109
+
110
+ async function animateIntroName(name, width) {
111
+ const label = `Welcome ${name}`;
112
+ const safeWidth = Math.max(width, label.length + 10);
113
+ const leftPadding = Math.floor((safeWidth - 2 - label.length) / 2);
114
+ const rightPadding = safeWidth - 2 - label.length - leftPadding;
115
+ const border = BOX.topLeft + BOX.horizontal.repeat(safeWidth - 2) + BOX.topRight;
116
+ const bottom = BOX.bottomLeft + BOX.horizontal.repeat(safeWidth - 2) + BOX.bottomRight;
117
+ const content = BOX.vertical + ' '.repeat(leftPadding) + label + ' '.repeat(rightPadding) + BOX.vertical;
118
+
119
+ if (!canAnimate()) {
120
+ console.log(border);
121
+ console.log(content);
122
+ console.log(bottom);
123
+ return;
124
+ }
125
+
126
+ const frames = [
127
+ `Starting ${name}`,
128
+ `Loading ${name}`,
129
+ `Preparing ${name}`,
130
+ `Welcome ${name}`
131
+ ];
132
+
133
+ for (const frame of frames) {
134
+ writeAnimatedLine(frame);
135
+ await sleep(180);
136
+ }
137
+ clearLine();
138
+
139
+ await typeAnimatedLines([border, content, bottom], 4, 45);
140
+ console.log('');
141
+ }
142
+
143
+ function openBrowser(url) {
144
+ const platform = os.platform();
145
+ let command;
146
+
147
+ switch (platform) {
148
+ case 'win32':
149
+ command = `start "" "${url}"`;
150
+ break;
151
+ case 'darwin':
152
+ command = `open "${url}"`;
153
+ break;
154
+ case 'linux':
155
+ command = `xdg-open "${url}"`;
156
+ break;
157
+ default:
158
+ console.error(`Unsupported platform: ${platform}`);
159
+ return;
160
+ }
161
+
162
+ exec(command, (error) => {
163
+ if (error) {
164
+ console.error(`Failed to open URL: ${error.message}`);
165
+ }
166
+ });
167
+ }
168
+
169
+ function isValidUrl(urlString) {
170
+ try {
171
+ new URL(urlString);
172
+ return true;
173
+ } catch (e) {
174
+ return false;
175
+ }
176
+ }
177
+
178
+ function isSystemFolder(dirPath) {
179
+ const home = os.homedir();
180
+ const systemFolders = [
181
+ home,
182
+ path.join(home, 'Downloads'),
183
+ path.join(home, 'Desktop'),
184
+ path.join(home, 'Documents'),
185
+ path.join(home, 'Pictures'),
186
+ path.join(home, 'Music'),
187
+ path.join(home, 'Videos')
188
+ ];
189
+
190
+ return systemFolders.some(folder =>
191
+ path.normalize(dirPath).toLowerCase() === path.normalize(folder).toLowerCase()
192
+ );
193
+ }
194
+
195
+ function loadEnv() {
196
+ const envPath = path.join(process.cwd(), '.env');
197
+ if (fs.existsSync(envPath)) {
198
+ const content = fs.readFileSync(envPath, 'utf8');
199
+ content.split('\n').forEach(line => {
200
+ const trimmed = line.trim();
201
+ if (trimmed && !trimmed.startsWith('#')) {
202
+ const [key, ...valueParts] = trimmed.split('=');
203
+ if (key && valueParts.length > 0) {
204
+ process.env[key.trim()] = valueParts.join('=').trim();
205
+ }
206
+ }
207
+ });
208
+ }
209
+ }
210
+
211
+ function saveEnvValue(key, value) {
212
+ const envPath = path.join(process.cwd(), '.env');
213
+ const normalizedValue = String(value ?? '').trim();
214
+ const existing = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
215
+ const lines = existing ? existing.split(/\r?\n/) : [];
216
+ let found = false;
217
+
218
+ const updatedLines = lines.map((line) => {
219
+ const trimmed = line.trim();
220
+ if (!trimmed || trimmed.startsWith('#')) {
221
+ return line;
222
+ }
223
+
224
+ const [existingKey] = trimmed.split('=');
225
+ if (existingKey && existingKey.trim() === key) {
226
+ found = true;
227
+ return `${key}=${normalizedValue}`;
228
+ }
229
+ return line;
230
+ });
231
+
232
+ if (!found) {
233
+ updatedLines.push(`${key}=${normalizedValue}`);
234
+ }
235
+
236
+ fs.writeFileSync(envPath, `${updatedLines.filter((line, index, array) => !(index === array.length - 1 && line === '')).join('\n')}\n`, 'utf8');
237
+ process.env[key] = normalizedValue;
238
+ }
239
+
240
+ function generateBox(title, lines, width) {
241
+ const safeWidth = Math.max(width, title.length + 6);
242
+ const top = `${BOX.topLeft}${BOX.horizontal.repeat(2)} ${title} ${BOX.horizontal.repeat(safeWidth - title.length - 6)}${BOX.horizontal}${BOX.topRight}`;
243
+ const bottom = `${BOX.bottomLeft}${BOX.horizontal.repeat(safeWidth - 2)}${BOX.bottomRight}`;
244
+ const paddedLines = lines.map(line => {
245
+ const content = line.slice(0, safeWidth - 4);
246
+ return `${BOX.vertical} ${content}${' '.repeat(safeWidth - 4 - content.length)} ${BOX.vertical}`;
247
+ });
248
+
249
+ if (paddedLines.length === 0) {
250
+ paddedLines.push(`${BOX.vertical} ${' '.repeat(safeWidth - 4)} ${BOX.vertical}`);
251
+ }
252
+
253
+ return [top, ...paddedLines, bottom];
254
+ }
255
+
256
+ function fitText(text, width) {
257
+ const clean = String(text ?? '');
258
+ if (clean.length <= width) {
259
+ return clean + ' '.repeat(width - clean.length);
260
+ }
261
+
262
+ if (width <= 1) {
263
+ return clean.slice(0, width);
264
+ }
265
+
266
+ return clean.slice(0, width - 1) + '\u2026';
267
+ }
268
+
269
+ function dashboardLine(left, width) {
270
+ return `${BOX.vertical} ${fitText(left, width - 4)} ${BOX.vertical}`;
271
+ }
272
+
273
+ function dashboardSeparator(width) {
274
+ return `${BOX.leftJoin}${BOX.horizontal.repeat(width - 2)}${BOX.rightJoin}`;
275
+ }
276
+
277
+ function dashboardBorder(width, top = true) {
278
+ const left = top ? BOX.topLeft : BOX.bottomLeft;
279
+ const right = top ? BOX.topRight : BOX.bottomRight;
280
+ return left + BOX.horizontal.repeat(width - 2) + right;
281
+ }
282
+
283
+ function sideBySide(leftLines, rightLines, gap = 2) {
284
+ const height = Math.max(leftLines.length, rightLines.length);
285
+ const leftWidth = leftLines[0]?.length || 0;
286
+ const rightWidth = rightLines[0]?.length || 0;
287
+ const output = [];
288
+ for (let i = 0; i < height; i++) {
289
+ const left = leftLines[i] || ' '.repeat(leftWidth);
290
+ const right = rightLines[i] || ' '.repeat(rightWidth);
291
+ output.push(left + ' '.repeat(gap) + right);
292
+ }
293
+ return output;
294
+ }
295
+
296
+ function equalizeBoxes(leftLines, rightLines) {
297
+ const left = [...leftLines];
298
+ const right = [...rightLines];
299
+
300
+ while (left.length < right.length) {
301
+ insertBlankBoxLine(left);
302
+ }
303
+
304
+ while (right.length < left.length) {
305
+ insertBlankBoxLine(right);
306
+ }
307
+
308
+ return [left, right];
309
+ }
310
+
311
+ function insertBlankBoxLine(lines) {
312
+ if (lines.length < 2) return;
313
+
314
+ const width = lines[0].length;
315
+ const blank = `${BOX.vertical} ${' '.repeat(width - 4)} ${BOX.vertical}`;
316
+ lines.splice(lines.length - 1, 0, blank);
317
+ }
318
+
319
+ function formatDashboardHeader(width) {
320
+ const now = new Date();
321
+ const dateStr = now.toLocaleDateString('en-US', { month: 'short', day: '2-digit' }).toUpperCase();
322
+ const timeStr = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true });
323
+ const dateTime = `${dateStr} ${timeStr}`;
324
+
325
+ const menu = ' LINK | PUSH | TODO | NEWS | HELP ';
326
+ const space = width - menu.length - dateTime.length - 4;
327
+
328
+ const content = `${BOX.vertical} ${menu}${' '.repeat(Math.max(0, space))}${dateTime} ${BOX.vertical}`;
329
+ const border = BOX.topLeft + BOX.horizontal.repeat(width - 2) + BOX.topRight;
330
+ const bottom = BOX.bottomLeft + BOX.horizontal.repeat(width - 2) + BOX.bottomRight;
331
+
332
+ return [border, content, bottom];
333
+ }
334
+
335
+ function formatConnectedDashboard({ width, menu, dateTime, todoLines, shortcutLines, newsLines }) {
336
+ const leftWidth = 38;
337
+ const rightWidth = width - leftWidth - 5;
338
+ const top = dashboardBorder(width, true);
339
+ const bottom = dashboardBorder(width, false);
340
+ const columnSeparator = BOX.leftJoin + BOX.horizontal.repeat(leftWidth) + BOX.cross + BOX.horizontal.repeat(rightWidth) + BOX.rightJoin;
341
+ const lines = [top];
342
+ const headerGap = width - menu.length - dateTime.length - 4;
343
+
344
+ lines.push(`${BOX.vertical} ${menu}${' '.repeat(Math.max(0, headerGap))}${dateTime} ${BOX.vertical}`);
345
+ lines.push(dashboardSeparator(width));
346
+
347
+ const leftTitle = 'ACTIVE TASKS (todo)';
348
+ const rightTitle = 'SHORTCUTS (links)';
349
+
350
+ lines.push(`${BOX.vertical} ${fitText(leftTitle, leftWidth - 2)} ${BOX.vertical} ${fitText(rightTitle, rightWidth - 2)} ${BOX.vertical}`);
351
+ lines.push(columnSeparator);
352
+
353
+ const rowCount = Math.max(todoLines.length, shortcutLines.length, 4);
354
+ for (let i = 0; i < rowCount; i++) {
355
+ lines.push(`${BOX.vertical} ${fitText(todoLines[i] || '', leftWidth - 2)} ${BOX.vertical} ${fitText(shortcutLines[i] || '', rightWidth - 2)} ${BOX.vertical}`);
356
+ }
357
+
358
+ lines.push(dashboardSeparator(width));
359
+ lines.push(dashboardLine('LATEST NEWS (news)', width));
360
+ lines.push(dashboardSeparator(width));
361
+
362
+ newsLines.forEach(line => {
363
+ lines.push(dashboardLine(line, width));
364
+ });
365
+
366
+ lines.push(bottom);
367
+ return lines;
368
+ }
369
+
370
+ module.exports = {
371
+ animateMessage,
372
+ withSpinner,
373
+ printAnimatedLines,
374
+ typeAnimatedLines,
375
+ animateIntroName,
376
+ openBrowser,
377
+ isValidUrl,
378
+ isSystemFolder,
379
+ loadEnv,
380
+ saveEnvValue,
381
+ generateBox,
382
+ formatConnectedDashboard,
383
+ sideBySide,
384
+ equalizeBoxes,
385
+ formatDashboardHeader
386
+ };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "error-ux-cli",
3
+ "version": "1.0.0",
4
+ "description": "All-in-one developer productivity CLI",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "error-ux-cli": "index.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node index.js",
11
+ "test": "node test/commands.test.js"
12
+ },
13
+ "files": [
14
+ "index.js",
15
+ "lib",
16
+ "README.md"
17
+ ],
18
+ "keywords": [
19
+ "cli",
20
+ "developer",
21
+ "productivity"
22
+ ],
23
+ "author": "Aniruth",
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "dependencies": {
29
+ "ink": "^7.0.0",
30
+ "react": "^19.2.5"
31
+ }
32
+ }