@nangabhaalu/andu 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/README.md +72 -0
- package/package.json +35 -0
- package/src/cli.js +165 -0
- package/src/data.js +69 -0
- package/src/screens.js +201 -0
- package/src/ui.js +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# โก Retro Portfolio CLI
|
|
2
|
+
|
|
3
|
+
A **cyber-arcade style terminal portfolio** you can run instantly with `npx`.
|
|
4
|
+
Built with Node.js โ retro neon aesthetics, smooth animations, and interactive navigation.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
## ๐ Quick Start
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx retro-portfolio
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
That's it. No install, no setup, no config.
|
|
17
|
+
|
|
18
|
+
## ๐ฅ What You Get
|
|
19
|
+
|
|
20
|
+
- **Boot animation** โ scanline flicker + loading spinner
|
|
21
|
+
- **ASCII art banner** โ your name in massive gradient text
|
|
22
|
+
- **Interactive menu** โ arrow-key navigation across 5 sections
|
|
23
|
+
- **Projects gallery** โ browse & open GitHub repos in your browser
|
|
24
|
+
- **Social links** โ Instagram, X, LinkedIn, Discord, GitHub
|
|
25
|
+
- **Spotify playlists** โ curated mood playlists, open with one click
|
|
26
|
+
- **About page** โ bio, skills, interests, current project
|
|
27
|
+
- **Retro styling** โ neon cyan/magenta/green/amber on dark backgrounds
|
|
28
|
+
|
|
29
|
+
## ๐ Local Development
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git clone https://github.com/yourusername/retro-portfolio-cli.git
|
|
33
|
+
cd retro-portfolio-cli
|
|
34
|
+
npm install
|
|
35
|
+
npm start
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## โ๏ธ Customisation
|
|
39
|
+
|
|
40
|
+
Edit `src/data.js` to replace placeholder content with your own:
|
|
41
|
+
|
|
42
|
+
- `profile` โ name, tagline, bio, skills, interests
|
|
43
|
+
- `projects` โ GitHub repos with descriptions
|
|
44
|
+
- `socials` โ social media links
|
|
45
|
+
- `playlists` โ Spotify playlist URLs
|
|
46
|
+
|
|
47
|
+
## ๐ฆ Publishing to npm
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm login
|
|
51
|
+
npm publish
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Users can then run `npx retro-portfolio` to experience your portfolio.
|
|
55
|
+
|
|
56
|
+
## ๐ Project Structure
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
retro-portfolio-cli/
|
|
60
|
+
โโโ package.json # npm manifest & bin entry
|
|
61
|
+
โโโ .gitignore
|
|
62
|
+
โโโ README.md
|
|
63
|
+
โโโ src/
|
|
64
|
+
โโโ cli.js # entry point โ boot, banner, menu loop
|
|
65
|
+
โโโ data.js # placeholder portfolio data
|
|
66
|
+
โโโ screens.js # section renderers (home, projects, etc.)
|
|
67
|
+
โโโ ui.js # colour helpers, dividers, animations
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nangabhaalu/andu",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "โก ANDU CLI โ PROMPT & LET AI THINK V1.1 โ Interactive Terminal Experience",
|
|
5
|
+
"bin": {
|
|
6
|
+
"andu": "src/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "src/cli.js",
|
|
9
|
+
"homepage": "https://github.com/AnujDubeyy/andu-cli",
|
|
10
|
+
"author": "Anuj Dubey",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cli",
|
|
14
|
+
"portfolio",
|
|
15
|
+
"terminal",
|
|
16
|
+
"npx",
|
|
17
|
+
"interactive",
|
|
18
|
+
"anuj-dubey",
|
|
19
|
+
"andu"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"start": "node src/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"boxen": "^7.1.1",
|
|
27
|
+
"chalk": "^5.3.0",
|
|
28
|
+
"figlet": "^1.7.0",
|
|
29
|
+
"gradient-string": "^2.0.2",
|
|
30
|
+
"inquirer": "^9.2.12",
|
|
31
|
+
"nanospinner": "^1.1.0",
|
|
32
|
+
"open": "^10.1.0",
|
|
33
|
+
"ora": "^7.0.1"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import gradient from 'gradient-string';
|
|
4
|
+
import figlet from 'figlet';
|
|
5
|
+
import { createSpinner } from 'nanospinner';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
|
|
8
|
+
import { t, sleep, typeText, clearScreen, checkTerminalSize, DIVIDER, currentTheme, setTheme } from './ui.js';
|
|
9
|
+
import { projects, socials, playlists, profile, quotes } from './data.js';
|
|
10
|
+
import { showProjects, showSocials, showSpotify, showAbout } from './screens.js';
|
|
11
|
+
|
|
12
|
+
function setupExitHandlers() {
|
|
13
|
+
process.on('SIGINT', () => {
|
|
14
|
+
console.log(t.dim('\n\n โฆ Signal received. Shutting down gracefully...\n'));
|
|
15
|
+
process.exit(0);
|
|
16
|
+
});
|
|
17
|
+
process.on('uncaughtException', (err) => {
|
|
18
|
+
console.error(t.secondary(`\n โ Unexpected error: ${err.message}\n`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const getGradient = () => {
|
|
24
|
+
return currentTheme === 'loki'
|
|
25
|
+
? gradient([{ color: '#39FF14', pos: 0 }, { color: '#FFD700', pos: 1 }])
|
|
26
|
+
: gradient([{ color: '#FF0000', pos: 0 }, { color: '#0000FF', pos: 1 }]);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
async function bootSequence() {
|
|
30
|
+
clearScreen();
|
|
31
|
+
checkTerminalSize();
|
|
32
|
+
|
|
33
|
+
console.log();
|
|
34
|
+
for (let i = 0; i < 3; i++) {
|
|
35
|
+
process.stdout.write(t.dim(' โโโโโโโ AWAKENING GOD PROTOCOL โโโโโโโ'));
|
|
36
|
+
await sleep(120);
|
|
37
|
+
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
38
|
+
await sleep(80);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const gColor = (text) => t.primary(text);
|
|
42
|
+
const spinner = createSpinner(gColor(' Requesting God api credits...')).start();
|
|
43
|
+
await sleep(600);
|
|
44
|
+
spinner.update({ text: gColor(' Forging timelines...') });
|
|
45
|
+
await sleep(500);
|
|
46
|
+
spinner.update({ text: gColor(' Injecting free-thinking logic...') });
|
|
47
|
+
await sleep(500);
|
|
48
|
+
spinner.update({ text: gColor(' Rendering interface...') });
|
|
49
|
+
await sleep(400);
|
|
50
|
+
spinner.success({ text: t.primary(' Protocol Online. Realizing god doesnt exist...\n') });
|
|
51
|
+
|
|
52
|
+
await sleep(300);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function showBanner() {
|
|
56
|
+
const bannerText = figlet.textSync(profile.name, {
|
|
57
|
+
font: 'ANSI Shadow',
|
|
58
|
+
horizontalLayout: 'fitted',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log(getGradient().multiline(bannerText));
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(t.dim(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
64
|
+
console.log(t.dim(' โ') + t.primary(' โก ANDU CLI - PROMPT & LET AI THINK V1.1 ') + t.dim('โ'));
|
|
65
|
+
console.log(t.dim(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
66
|
+
console.log();
|
|
67
|
+
await typeText(t.neutral(' > ' + profile.tagline), 20);
|
|
68
|
+
console.log();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function mainMenu() {
|
|
72
|
+
while (true) {
|
|
73
|
+
clearScreen();
|
|
74
|
+
await showBanner();
|
|
75
|
+
|
|
76
|
+
const q = quotes[Math.floor(Math.random() * quotes.length)] + " ~ Anuj Dubey";
|
|
77
|
+
|
|
78
|
+
const choices = [
|
|
79
|
+
{ name: `${t.primary('โ')} ${t.bold('Projects')} ${t.dim('โ crewmap & the future')}`, value: 'projects' },
|
|
80
|
+
{ name: `${t.primary('โ')} ${t.bold('Socials')} ${t.dim('โ x / blog / behance')}`, value: 'socials' },
|
|
81
|
+
{ name: `${t.primary('โซ')} ${t.bold('Spotify')} ${t.dim('โ curated playlists')}`, value: 'spotify' },
|
|
82
|
+
{ name: `${t.primary('โ')} ${t.bold('About me')} ${t.dim('โ ahh geez')}`, value: 'about' },
|
|
83
|
+
new inquirer.Separator(DIVIDER()),
|
|
84
|
+
{ name: `${t.secondary('โฏ')} ${t.bold('Different Shaders')} ${t.dim('โ Toggle theme colors')}`, value: 'theme' },
|
|
85
|
+
{ name: `${t.secondary('โป')} ${t.bold('Exit')} ${t.dim('โ see you later')}`, value: 'exit' },
|
|
86
|
+
new inquirer.Separator(' '),
|
|
87
|
+
new inquirer.Separator(t.dim(' ใ ' + q + ' ใ'))
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const { section } = await inquirer.prompt([
|
|
91
|
+
{
|
|
92
|
+
type: 'list',
|
|
93
|
+
name: 'section',
|
|
94
|
+
message: getGradient()(' โ MAIN MENU โบ'),
|
|
95
|
+
choices,
|
|
96
|
+
pageSize: 15,
|
|
97
|
+
loop: false,
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
clearScreen();
|
|
102
|
+
|
|
103
|
+
switch (section) {
|
|
104
|
+
case 'projects':
|
|
105
|
+
await showProjects(projects);
|
|
106
|
+
break;
|
|
107
|
+
case 'socials':
|
|
108
|
+
await showSocials(socials);
|
|
109
|
+
break;
|
|
110
|
+
case 'spotify':
|
|
111
|
+
await showSpotify(playlists);
|
|
112
|
+
break;
|
|
113
|
+
case 'about':
|
|
114
|
+
await showAbout();
|
|
115
|
+
break;
|
|
116
|
+
case 'theme':
|
|
117
|
+
setTheme(currentTheme === 'loki' ? 'spider' : 'loki');
|
|
118
|
+
continue;
|
|
119
|
+
case 'exit':
|
|
120
|
+
console.log();
|
|
121
|
+
// 1. Scanline glitch effect (mirrors bootSequence)
|
|
122
|
+
for (let i = 0; i < 3; i++) {
|
|
123
|
+
process.stdout.write(t.dim(' โโโโโโโ TERMINATING PROTOCOLS โโโโโโโ'));
|
|
124
|
+
await sleep(120);
|
|
125
|
+
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
126
|
+
await sleep(80);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const crashSequence = [
|
|
130
|
+
"tryin to log out...",
|
|
131
|
+
"rolling out...",
|
|
132
|
+
"1 2 3 4 5...",
|
|
133
|
+
"67!",
|
|
134
|
+
" *LAUGHS*",
|
|
135
|
+
" *HAHAH*",
|
|
136
|
+
" *DIES*"
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
for (let i = 0; i < crashSequence.length; i++) {
|
|
140
|
+
const text = crashSequence[i];
|
|
141
|
+
const isError = i === crashSequence.length - 1;
|
|
142
|
+
const display = isError ? t.primary(` โ${text}`) : t.secondary(` โ ง${text}`);
|
|
143
|
+
|
|
144
|
+
process.stdout.write('\r' + ' '.repeat(50)); // clear current line space
|
|
145
|
+
process.stdout.write('\r' + display);
|
|
146
|
+
await sleep(400);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log('\n');
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log();
|
|
154
|
+
console.log(DIVIDER());
|
|
155
|
+
console.log();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function main() {
|
|
160
|
+
setupExitHandlers();
|
|
161
|
+
await bootSequence();
|
|
162
|
+
await mainMenu();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
main();
|
package/src/data.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export const profile = {
|
|
2
|
+
name: 'ANUJ DUBEY',
|
|
3
|
+
tagline: 'Performative Engg, Free-Thinker',
|
|
4
|
+
bio: [
|
|
5
|
+
'i am 20yo performative engg who gambles on LLM to spit out right code.',
|
|
6
|
+
'i am also a full stack developer and tech enthusiat until my api credits resist.',
|
|
7
|
+
'i am moved by current developments in ai world (quite literally).',
|
|
8
|
+
'my hobbies include watching ai code, crying about job market, crying about my current degree,',
|
|
9
|
+
'planning for my master to delay unemployment and being scared the shit out of everything',
|
|
10
|
+
'24*7 while still doing nothing.',
|
|
11
|
+
],
|
|
12
|
+
currentlyBuilding: 'CLI, CrewMap... (2026 is gonna be my year)',
|
|
13
|
+
skills: [
|
|
14
|
+
'JavaScript / TypeScript',
|
|
15
|
+
'React ยท Next.js ยท Node',
|
|
16
|
+
'Thinking Outside The Box',
|
|
17
|
+
'Mischief & Magic'
|
|
18
|
+
],
|
|
19
|
+
interests: [
|
|
20
|
+
'Open Source Contribution where I open and you contribute.',
|
|
21
|
+
'Free thinkers - made to ragebait two polar opposite groups on the internet.',
|
|
22
|
+
'Cinephile - has seen Interstellar twice, I have a Letterboxd account.',
|
|
23
|
+
'Designer - I use Pinterest.',
|
|
24
|
+
'Philosopher - born to be Diogenes, forced to be Sisyphus.'
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const quotes = [
|
|
29
|
+
'"i lowkey hate myself"',
|
|
30
|
+
'"love is like fart when released u realize it\'s shit"',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export const projects = [
|
|
34
|
+
{
|
|
35
|
+
name: 'CrewMap',
|
|
36
|
+
description: 'Collaborative team mapping and management web app.',
|
|
37
|
+
url: 'https://github.com/AnujDubeyy/crewmap',
|
|
38
|
+
tags: ['React', 'Collaboration'],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'Bluecheck Advisory',
|
|
42
|
+
description: 'Advisory and consulting platform architecture.',
|
|
43
|
+
url: 'https://github.com/AnujDubeyy',
|
|
44
|
+
tags: ['Web', 'Advisory'],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'Rastogi',
|
|
48
|
+
description: 'Custom client implementation platform.',
|
|
49
|
+
url: 'https://github.com/AnujDubeyy',
|
|
50
|
+
tags: ['Client', 'Full-Stack'],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'More to come...',
|
|
54
|
+
description: '2026 is gonna be my year.',
|
|
55
|
+
url: 'https://github.com/AnujDubeyy',
|
|
56
|
+
tags: ['Future', '2026'],
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export const socials = [
|
|
61
|
+
{ name: 'X (Twitter)', icon: '๐ ', url: 'https://x.com/AnujDubey186224' },
|
|
62
|
+
{ name: 'Behance', icon: '๐จ', url: 'https://www.behance.net/anujdubey9' },
|
|
63
|
+
{ name: 'Blog', icon: '๐', url: 'https://jainaloo.blogspot.com/2025/04/who-tf-r-umaybe-part-1.html' },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
export const playlists = [
|
|
67
|
+
{ name: '๐ง Playlist 1', mood: '', url: 'https://open.spotify.com/playlist/2d0iemtr4B3G4XZBGnsdDr?si=OF40YORTTLq2OB8Uxn6QSQ' },
|
|
68
|
+
{ name: '๐ง Playlist 2', mood: '', url: 'https://open.spotify.com/playlist/2d0iemtr4B3G4XZBGnsdDr?si=OF40YORTTLq2OB8Uxn6QSQ' },
|
|
69
|
+
];
|
package/src/screens.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { t, sectionTitle, DIVIDER, sleep, typeText, currentTheme } from './ui.js';
|
|
2
|
+
import { profile } from './data.js';
|
|
3
|
+
import boxen from 'boxen';
|
|
4
|
+
import open from 'open';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
|
|
7
|
+
// โโโ Projects โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
8
|
+
export async function showProjects(projects) {
|
|
9
|
+
console.log(sectionTitle('PROJECTS'));
|
|
10
|
+
console.log();
|
|
11
|
+
|
|
12
|
+
const choices = projects.map((p, i) => ({
|
|
13
|
+
name: `${t.primary(p.name)} ${t.dim('โ')} ${t.neutral(p.description)} ${t.dim(p.tags.map(tag => `[${tag}]`).join(' '))}`,
|
|
14
|
+
value: i,
|
|
15
|
+
}));
|
|
16
|
+
choices.push(new inquirer.Separator(DIVIDER()));
|
|
17
|
+
choices.push({ name: t.secondary('โ Back to menu'), value: -1 });
|
|
18
|
+
|
|
19
|
+
const { idx } = await inquirer.prompt([
|
|
20
|
+
{
|
|
21
|
+
type: 'list',
|
|
22
|
+
name: 'idx',
|
|
23
|
+
message: t.secondary('Select a project to view details:'),
|
|
24
|
+
choices,
|
|
25
|
+
pageSize: 10,
|
|
26
|
+
loop: false,
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
if (idx === -1) return;
|
|
31
|
+
|
|
32
|
+
const project = projects[idx];
|
|
33
|
+
console.log();
|
|
34
|
+
console.log(
|
|
35
|
+
boxen(
|
|
36
|
+
t.bold(t.primary(project.name)) + '\n\n' +
|
|
37
|
+
t.neutral(project.description) + '\n\n' +
|
|
38
|
+
t.dim('URL: ') + t.secondary(project.url) + '\n' +
|
|
39
|
+
t.dim('Tags: ') + project.tags.map(tg => t.primary(`[${tg}]`)).join(' '),
|
|
40
|
+
{
|
|
41
|
+
padding: 1,
|
|
42
|
+
margin: { left: 2 },
|
|
43
|
+
borderStyle: 'round',
|
|
44
|
+
borderColor: currentTheme === 'loki' ? 'green' : 'blue',
|
|
45
|
+
title: '๐ PROJECT DETAIL',
|
|
46
|
+
titleAlignment: 'center',
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const { action } = await inquirer.prompt([
|
|
52
|
+
{
|
|
53
|
+
type: 'list',
|
|
54
|
+
name: 'action',
|
|
55
|
+
message: t.secondary('What next?'),
|
|
56
|
+
choices: [
|
|
57
|
+
{ name: t.primary('๐ Open in browser'), value: 'open' },
|
|
58
|
+
{ name: t.secondary('โ Back to projects'), value: 'back' },
|
|
59
|
+
{ name: t.secondary('โ Back to menu'), value: 'menu' },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
if (action === 'open') {
|
|
65
|
+
try {
|
|
66
|
+
await open(project.url);
|
|
67
|
+
console.log(t.primary(` โ Opened ${project.url}`));
|
|
68
|
+
} catch {
|
|
69
|
+
console.log(t.secondary(` โ Could not open URL: ${project.url}`));
|
|
70
|
+
}
|
|
71
|
+
await sleep(800);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (action === 'back') {
|
|
75
|
+
await showProjects(projects);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// โโโ Socials โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
80
|
+
export async function showSocials(socials) {
|
|
81
|
+
console.log(sectionTitle('SOCIALS'));
|
|
82
|
+
console.log();
|
|
83
|
+
|
|
84
|
+
const choices = socials.map(s => ({
|
|
85
|
+
name: `${s.icon} ${t.primary(s.name)} ${t.dim('โ')} ${t.dim(s.url)}`,
|
|
86
|
+
value: s.url,
|
|
87
|
+
}));
|
|
88
|
+
choices.push(new inquirer.Separator(DIVIDER()));
|
|
89
|
+
choices.push({ name: t.secondary('โ Back to menu'), value: 'back' });
|
|
90
|
+
|
|
91
|
+
const { url } = await inquirer.prompt([
|
|
92
|
+
{
|
|
93
|
+
type: 'list',
|
|
94
|
+
name: 'url',
|
|
95
|
+
message: t.secondary('Open a social profile:'),
|
|
96
|
+
choices,
|
|
97
|
+
loop: false,
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
if (url === 'back') return;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
await open(url);
|
|
105
|
+
console.log(t.primary(` โ Opened ${url}`));
|
|
106
|
+
} catch {
|
|
107
|
+
console.log(t.secondary(` โ Could not open URL: ${url}`));
|
|
108
|
+
}
|
|
109
|
+
await sleep(600);
|
|
110
|
+
await showSocials(socials);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// โโโ Spotify โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
114
|
+
export async function showSpotify(playlists) {
|
|
115
|
+
console.log(sectionTitle('SPOTIFY'));
|
|
116
|
+
console.log();
|
|
117
|
+
|
|
118
|
+
const choices = playlists.map(p => ({
|
|
119
|
+
name: t.primary(p.name),
|
|
120
|
+
value: p.url,
|
|
121
|
+
}));
|
|
122
|
+
choices.push(new inquirer.Separator(DIVIDER()));
|
|
123
|
+
choices.push({ name: t.secondary('โ Back to menu'), value: 'back' });
|
|
124
|
+
|
|
125
|
+
const { url } = await inquirer.prompt([
|
|
126
|
+
{
|
|
127
|
+
type: 'list',
|
|
128
|
+
name: 'url',
|
|
129
|
+
message: t.secondary('Pick a playlist to open:'),
|
|
130
|
+
choices,
|
|
131
|
+
loop: false,
|
|
132
|
+
},
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
if (url === 'back') return;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await open(url);
|
|
139
|
+
console.log(t.primary(` โ Opening playlist in Spotify...`));
|
|
140
|
+
} catch {
|
|
141
|
+
console.log(t.secondary(` โ Could not open URL: ${url}`));
|
|
142
|
+
}
|
|
143
|
+
await sleep(600);
|
|
144
|
+
await showSpotify(playlists);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// โโโ About โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
148
|
+
export async function showAbout() {
|
|
149
|
+
console.log(sectionTitle('ABOUT ME'));
|
|
150
|
+
console.log();
|
|
151
|
+
|
|
152
|
+
// Combine bio and "Currently building"
|
|
153
|
+
const bioText = profile.bio.map(l => ' > ' + l).join('\n');
|
|
154
|
+
const loreContent = bioText + '\n\n' +
|
|
155
|
+
t.dim(' Currently building: ') + t.primary(profile.currentlyBuilding);
|
|
156
|
+
|
|
157
|
+
const loreBox = boxen(loreContent, {
|
|
158
|
+
padding: 1,
|
|
159
|
+
margin: { left: 2 },
|
|
160
|
+
borderStyle: 'round',
|
|
161
|
+
borderColor: currentTheme === 'loki' ? 'green' : 'red',
|
|
162
|
+
title: 'THE LORE',
|
|
163
|
+
titleAlignment: 'center',
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Combine interests
|
|
167
|
+
const interestsText = profile.interests.map(i => ` ${t.primary('ยป')} ${t.neutral(i)}`).join('\n');
|
|
168
|
+
const interestsBox = boxen(interestsText, {
|
|
169
|
+
padding: 1,
|
|
170
|
+
margin: { left: 2 },
|
|
171
|
+
borderStyle: 'round',
|
|
172
|
+
borderColor: currentTheme === 'loki' ? 'yellow' : 'blue',
|
|
173
|
+
title: 'INTERESTS',
|
|
174
|
+
titleAlignment: 'center',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const loreLines = loreBox.split('\n');
|
|
178
|
+
for (const line of loreLines) {
|
|
179
|
+
console.log(line);
|
|
180
|
+
await sleep(25);
|
|
181
|
+
}
|
|
182
|
+
console.log();
|
|
183
|
+
|
|
184
|
+
const intLines = interestsBox.split('\n');
|
|
185
|
+
for (const line of intLines) {
|
|
186
|
+
console.log(line);
|
|
187
|
+
await sleep(25);
|
|
188
|
+
}
|
|
189
|
+
console.log();
|
|
190
|
+
|
|
191
|
+
await inquirer.prompt([
|
|
192
|
+
{
|
|
193
|
+
type: 'list',
|
|
194
|
+
name: 'action',
|
|
195
|
+
message: t.secondary('What next?'),
|
|
196
|
+
choices: [
|
|
197
|
+
{ name: t.secondary('โ Back to menu'), value: 'back' },
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
]);
|
|
201
|
+
}
|
package/src/ui.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export let currentTheme = 'loki'; // 'loki' or 'spider'
|
|
4
|
+
|
|
5
|
+
export const t = {
|
|
6
|
+
primary: (text) => {
|
|
7
|
+
if (currentTheme === 'loki') return chalk.hex('#39FF14')(text); // Green
|
|
8
|
+
if (currentTheme === 'spider') return chalk.hex('#FF0000')(text); // Red
|
|
9
|
+
},
|
|
10
|
+
secondary: (text) => {
|
|
11
|
+
if (currentTheme === 'loki') return chalk.hex('#FFD700')(text); // Gold
|
|
12
|
+
if (currentTheme === 'spider') return chalk.hex('#0000FF')(text); // Blue
|
|
13
|
+
},
|
|
14
|
+
accent: (text) => {
|
|
15
|
+
if (currentTheme === 'loki') return chalk.hex('#00FF00').bold(text);
|
|
16
|
+
if (currentTheme === 'spider') return chalk.hex('#FF0000').bold(text);
|
|
17
|
+
},
|
|
18
|
+
neutral: (text) => chalk.hex('#E0E0E0')(text),
|
|
19
|
+
dim: (text) => chalk.hex('#555566')(text),
|
|
20
|
+
bold: (text) => chalk.bold(text)
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function setTheme(themeName) {
|
|
24
|
+
currentTheme = themeName;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const DIVIDER = () => t.dim('โ'.repeat(60));
|
|
28
|
+
export const SCANLINE = () => t.dim('โ'.repeat(60));
|
|
29
|
+
|
|
30
|
+
export function sectionTitle(text) {
|
|
31
|
+
return `\n${DIVIDER()}\n ${t.secondary('โบ')} ${t.bold(t.primary(text))}\n${DIVIDER()}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function sleep(ms) {
|
|
35
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function typeText(text, delay = 18) {
|
|
39
|
+
for (const char of text) {
|
|
40
|
+
process.stdout.write(char);
|
|
41
|
+
await sleep(delay);
|
|
42
|
+
}
|
|
43
|
+
process.stdout.write('\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function checkTerminalSize() {
|
|
47
|
+
const cols = process.stdout.columns || 80;
|
|
48
|
+
const rows = process.stdout.rows || 24;
|
|
49
|
+
if (cols < 60 || rows < 20) {
|
|
50
|
+
console.log(
|
|
51
|
+
t.secondary('\nโ Your terminal is a bit small (' + cols + 'ร' + rows + ').')
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function clearScreen() {
|
|
57
|
+
process.stdout.write('\x1B[2J\x1B[3J\x1B[H');
|
|
58
|
+
}
|