himanshuchandola 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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +175 -0
  3. package/dist/index.js +291 -0
  4. package/package.json +80 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Himanshu Chandola
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # himanshuchandola-cli
2
+
3
+ Interactive CLI portfolio for Himanshu Chandola - A modern terminal experience built with TypeScript.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/himanshuchandola.svg)](https://www.npmjs.com/package/himanshuchandola)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue.svg)](https://www.typescriptlang.org/)
8
+
9
+ ## Overview
10
+
11
+ A TypeScript-based interactive CLI tool that serves as a terminal portfolio. Features animated ASCII banner, terminal card display, and interactive menu navigation to explore my digital presence.
12
+
13
+ ## Installation
14
+
15
+ ### Run without installing
16
+
17
+ ```bash
18
+ npx himanshuchandola
19
+ ```
20
+
21
+ ### Install globally
22
+
23
+ ```bash
24
+ npm install -g himanshuchandola
25
+ ```
26
+
27
+ Then run:
28
+
29
+ ```bash
30
+ himanshuchandola
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ```bash
36
+ himanshuchandola [options]
37
+ ```
38
+
39
+ ### Options
40
+
41
+ - `--open=<destination>` - Open a destination directly (portfolio, github, linkedin, peerlist, twitter)
42
+ - `--no-anim` - Skip the animated banner
43
+ - `-h, --help` - Show help message
44
+
45
+ ### Examples
46
+
47
+ ```bash
48
+ # Open GitHub directly
49
+ himanshuchandola --open=github
50
+
51
+ # Skip animation
52
+ himanshuchandola --no-anim
53
+
54
+ # Show help
55
+ himanshuchandola --help
56
+ ```
57
+
58
+ ## Features
59
+
60
+ - Animated ASCII banner with color cycling
61
+ - Beautiful terminal card with personal information
62
+ - Interactive numbered menu for navigation
63
+ - CLI flags for direct navigation and animation control
64
+ - Graceful fallbacks for non-TTY environments
65
+ - TypeScript with strict mode
66
+ - Comprehensive test coverage
67
+ - Automated versioning and publishing with semantic-release
68
+
69
+ ## Tech Stack
70
+
71
+ - **TypeScript** - Type-safe development with strict mode
72
+ - **tsup** - Fast TypeScript bundler
73
+ - **Biome** - Fast linter and formatter
74
+ - **Vitest** - Unit testing framework
75
+ - **Inquirer** - Interactive command-line prompts
76
+ - **Chalk** - Terminal string styling
77
+ - **Boxen** - Terminal boxes
78
+ - **Figlet** - ASCII art text
79
+ - **Ora** - Elegant terminal spinners
80
+ - **Semantic Release** - Automated versioning and npm publishing
81
+
82
+ ## Development
83
+
84
+ ### Prerequisites
85
+
86
+ - [Bun](https://bun.sh/) (recommended) or Node.js 18+
87
+
88
+ ### Setup
89
+
90
+ ```bash
91
+ # Clone the repository
92
+ git clone https://github.com/himanshuchandola/himanshuchandola-cli.git
93
+ cd himanshuchandola-cli
94
+
95
+ # Install dependencies
96
+ bun install
97
+
98
+ # Run in development mode
99
+ bun run dev
100
+
101
+ # Build
102
+ bun run build
103
+
104
+ # Test
105
+ bun test
106
+
107
+ # Lint
108
+ bun run lint
109
+ ```
110
+
111
+ ### Scripts
112
+
113
+ - `bun run dev` - Watch mode with auto-rebuild
114
+ - `bun run build` - Build for production
115
+ - `bun test` - Run tests
116
+ - `bun run type-check` - TypeScript type checking
117
+ - `bun run lint` - Lint code with Biome
118
+ - `bun run format` - Format code with Biome
119
+
120
+ ## Project Structure
121
+
122
+ ```
123
+ himanshuchandola-cli/
124
+ ├── src/
125
+ │ ├── index.ts # CLI entry point
126
+ │ ├── config/
127
+ │ │ └── personal.ts # Personal data configuration
128
+ │ ├── modules/
129
+ │ │ ├── banner.ts # Animated ASCII banner
130
+ │ │ ├── menu.ts # Interactive menu system
131
+ │ │ └── card.ts # Terminal card display
132
+ │ ├── utils/
133
+ │ │ ├── flags.ts # CLI flag parsing
134
+ │ │ ├── loader.ts # Dynamic module loading
135
+ │ │ └── open.ts # Safe URL opening
136
+ │ └── types/
137
+ │ └── index.ts # TypeScript type definitions
138
+ ├── tests/ # Test files
139
+ ├── docs/ # Documentation
140
+ ├── .github/workflows/ # CI/CD workflows
141
+ └── package.json
142
+ ```
143
+
144
+ ## Documentation
145
+
146
+ - [Development Guide](./docs/DEVELOPMENT.md)
147
+ - [Testing Guide](./docs/TESTING.md)
148
+ - [Contributing Guidelines](./docs/CONTRIBUTING.md)
149
+
150
+ ## Automated Publishing
151
+
152
+ This project uses semantic-release for automated versioning and npm publishing. Commits following the [Conventional Commits](https://www.conventionalcommits.org/) specification will trigger automated releases.
153
+
154
+ ### Commit Message Format
155
+
156
+ - `feat(scope): message` - Minor version bump
157
+ - `fix(scope): message` - Patch version bump
158
+ - `feat(scope)!: message` - Major version bump (breaking change)
159
+ - `docs(scope): message` - No version bump
160
+ - `chore(scope): message` - No version bump
161
+
162
+ Valid scopes: cli, config, deps, ci, types, utils, banner, menu, card, release
163
+
164
+ ## License
165
+
166
+ This project is [MIT](./LICENSE) licensed.
167
+
168
+ ## Author
169
+
170
+ **Himanshu Chandola**
171
+
172
+ - Portfolio: [himanshuchandola.dev](https://himanshuchandola.dev)
173
+ - GitHub: [@himanshuchandola](https://github.com/himanshuchandola)
174
+ - LinkedIn: [himanshuchandola](https://www.linkedin.com/in/himanshuchandola/)
175
+ - Twitter: [@himanshuistaken](https://x.com/himanshuistaken)
package/dist/index.js ADDED
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/config/personal.ts
4
+ var personalConfig = {
5
+ name: "Himanshu Chandola",
6
+ role: "Software Engineer",
7
+ company: "Exiliensoft Consulting Services",
8
+ portfolio: "https://himanshuchandola.dev",
9
+ links: {
10
+ github: "https://github.com/himanshuchandola",
11
+ linkedin: "https://www.linkedin.com/in/himanshuchandola/",
12
+ peerlist: "https://peerlist.io/himanshuhere",
13
+ twitter: "https://x.com/himanshuistaken"
14
+ }
15
+ };
16
+
17
+ // src/modules/banner.ts
18
+ import chalk from "chalk";
19
+
20
+ // src/utils/loader.ts
21
+ async function loadModule(moduleName) {
22
+ try {
23
+ const module = await import(moduleName);
24
+ return module.default || module;
25
+ } catch (_error) {
26
+ throw new Error(`Failed to load module: ${moduleName}`);
27
+ }
28
+ }
29
+
30
+ // src/modules/banner.ts
31
+ var bannerColors = [
32
+ chalk.hex("#667eea"),
33
+ // Purple
34
+ chalk.hex("#764ba2"),
35
+ // Deep purple
36
+ chalk.hex("#5b86e5"),
37
+ // Blue
38
+ chalk.hex("#36d1dc"),
39
+ // Cyan
40
+ chalk.hex("#667eea")
41
+ // Back to purple
42
+ ];
43
+ async function animateBanner(text, cycles = 2, delay = 100) {
44
+ for (let i = 0; i < cycles * bannerColors.length; i++) {
45
+ console.clear();
46
+ const colorFn = bannerColors[i % bannerColors.length];
47
+ if (colorFn) {
48
+ console.log(colorFn(text));
49
+ }
50
+ await new Promise((resolve) => setTimeout(resolve, delay));
51
+ }
52
+ }
53
+ async function showBanner(skipAnimation) {
54
+ if (skipAnimation) {
55
+ return;
56
+ }
57
+ try {
58
+ const figletModule = await loadModule("figlet");
59
+ const bannerText = figletModule.textSync("Hello There!", {
60
+ font: "Slant",
61
+ horizontalLayout: "default",
62
+ verticalLayout: "default"
63
+ });
64
+ await animateBanner(bannerText, 2, 100);
65
+ } catch (_error) {
66
+ console.log(chalk.bold.hex("#667eea")("\n=== Hello There! ===\n"));
67
+ }
68
+ }
69
+
70
+ // src/modules/card.ts
71
+ import boxen from "boxen";
72
+ import chalk2 from "chalk";
73
+ function createCard() {
74
+ const name = chalk2.bold.hex("#667eea")(personalConfig.name);
75
+ const role = chalk2.hex("#a8b2d1")(`${personalConfig.role} @ ${personalConfig.company}`);
76
+ const tagline = chalk2.hex("#8892b0")("Building elegant solutions, one line at a time");
77
+ const portfolio = chalk2.hex("#64ffda")(personalConfig.portfolio);
78
+ const github = chalk2.hex("#64ffda")(personalConfig.links.github);
79
+ const linkedin = chalk2.hex("#64ffda")(personalConfig.links.linkedin);
80
+ const peerlist = chalk2.hex("#64ffda")(personalConfig.links.peerlist);
81
+ const twitter = chalk2.hex("#64ffda")(personalConfig.links.twitter);
82
+ const width = Math.min(80, Math.max(40, (process.stdout.columns || 80) - 10));
83
+ const card = boxen(
84
+ [
85
+ `${name}`,
86
+ `${tagline}`,
87
+ "",
88
+ `${role}`,
89
+ "",
90
+ `${chalk2.hex("#a8b2d1")("Portfolio")} ${portfolio}`,
91
+ `${chalk2.hex("#a8b2d1")("GitHub")} ${github}`,
92
+ `${chalk2.hex("#a8b2d1")("LinkedIn")} ${linkedin}`,
93
+ `${chalk2.hex("#a8b2d1")("Peerlist")} ${peerlist}`,
94
+ `${chalk2.hex("#a8b2d1")("Twitter")} ${twitter}`
95
+ ].join("\n"),
96
+ {
97
+ padding: 1,
98
+ margin: 1,
99
+ borderStyle: "double",
100
+ borderColor: "#667eea",
101
+ width
102
+ }
103
+ );
104
+ return card;
105
+ }
106
+ function showCard() {
107
+ console.log(chalk2.hex("#8892b0")("\n Welcome to my interactive cli digital space\n"));
108
+ console.log(createCard());
109
+ }
110
+
111
+ // src/utils/open.ts
112
+ import open from "open";
113
+ async function safeOpen(url) {
114
+ try {
115
+ await open(url);
116
+ } catch (_error) {
117
+ console.log(`
118
+ Couldn't open automatically. Here's the link:
119
+ ${url}
120
+ `);
121
+ }
122
+ }
123
+
124
+ // src/modules/menu.ts
125
+ var menuChoices = [
126
+ { name: "\u2192 Visit Portfolio - Check out my work and projects", value: "portfolio" },
127
+ { name: "\u2192 GitHub Profile - Explore my open source contributions", value: "github" },
128
+ { name: "\u2192 LinkedIn - Let's connect professionally", value: "linkedin" },
129
+ { name: "\u2192 Peerlist - View my professional timeline", value: "peerlist" },
130
+ { name: "\u2192 Twitter/X - Follow for tech insights", value: "twitter" },
131
+ { name: "\u2190 Exit", value: "exit" }
132
+ ];
133
+ var destinationUrls = {
134
+ portfolio: personalConfig.portfolio,
135
+ github: personalConfig.links.github,
136
+ linkedin: personalConfig.links.linkedin,
137
+ peerlist: personalConfig.links.peerlist,
138
+ twitter: personalConfig.links.twitter
139
+ };
140
+ var loadingMessages = {
141
+ portfolio: "Loading portfolio...",
142
+ github: "Opening GitHub profile...",
143
+ linkedin: "Connecting to LinkedIn...",
144
+ peerlist: "Loading Peerlist profile...",
145
+ twitter: "Opening Twitter/X..."
146
+ };
147
+ async function showRedirectLoader(message, url) {
148
+ const ora = await loadModule("ora");
149
+ const cliSpinners = await loadModule("cli-spinners");
150
+ const loader = ora({
151
+ text: message,
152
+ spinner: cliSpinners.dots,
153
+ color: "magenta"
154
+ }).start();
155
+ setTimeout(async () => {
156
+ loader.succeed(message);
157
+ await safeOpen(url);
158
+ }, 1200);
159
+ }
160
+ async function showMenu() {
161
+ const inquirer = await loadModule("inquirer");
162
+ const ora = await loadModule("ora");
163
+ const cliSpinners = await loadModule("cli-spinners");
164
+ const menuLoader = ora({
165
+ text: "Initializing menu...",
166
+ spinner: cliSpinners.dots,
167
+ color: "magenta"
168
+ }).start();
169
+ setTimeout(() => {
170
+ menuLoader.stop();
171
+ const questions = [
172
+ {
173
+ type: "rawlist",
174
+ name: "action",
175
+ message: "What would you like to explore?",
176
+ choices: menuChoices
177
+ }
178
+ ];
179
+ inquirer.prompt(questions).then(async (answers) => {
180
+ const action = answers.action;
181
+ if (action === "exit") {
182
+ console.log("\u{1F44B} Thanks for stopping by\u2014see you around!");
183
+ return;
184
+ }
185
+ const url = destinationUrls[action];
186
+ const message = loadingMessages[action];
187
+ if (url && message) {
188
+ await showRedirectLoader(message, url);
189
+ }
190
+ }).catch((error) => {
191
+ if (error.message?.includes("SIGINT")) {
192
+ console.log("\u270B Caught Ctrl+C. Take care and have a great day!");
193
+ } else {
194
+ console.log("Prompt closed. Until next time! \u{1F44B}");
195
+ }
196
+ process.exitCode = 0;
197
+ });
198
+ }, 900);
199
+ }
200
+
201
+ // src/utils/flags.ts
202
+ function parseFlags(argv2) {
203
+ const flags2 = {
204
+ help: false,
205
+ noAnim: false
206
+ };
207
+ for (const arg of argv2) {
208
+ if (arg === "--help" || arg === "-h") {
209
+ flags2.help = true;
210
+ } else if (arg === "--no-anim") {
211
+ flags2.noAnim = true;
212
+ } else if (arg.startsWith("--open=")) {
213
+ const value = arg.slice(7);
214
+ if (value === "portfolio" || value === "github" || value === "linkedin" || value === "peerlist" || value === "twitter") {
215
+ flags2.open = value;
216
+ }
217
+ }
218
+ }
219
+ return flags2;
220
+ }
221
+ function showHelp() {
222
+ console.log(`
223
+ Usage: himanshuchandola [options]
224
+
225
+ Options:
226
+ --open=portfolio|github|linkedin|peerlist|twitter Open a link directly and exit
227
+ --no-anim Skip banner animation
228
+ -h, --help Show this help
229
+ `);
230
+ }
231
+
232
+ // src/index.ts
233
+ var isCI = process.env.CI === "true" || process.env.CI === "1";
234
+ var isInteractive = process.stdin.isTTY && process.stdout.isTTY;
235
+ var argv = process.argv.slice(2);
236
+ var flags = parseFlags(argv);
237
+ var destinationUrls2 = {
238
+ portfolio: personalConfig.portfolio,
239
+ github: personalConfig.links.github,
240
+ linkedin: personalConfig.links.linkedin,
241
+ peerlist: personalConfig.links.peerlist,
242
+ twitter: personalConfig.links.twitter
243
+ };
244
+ async function main() {
245
+ if (flags.help) {
246
+ showHelp();
247
+ process.exit(0);
248
+ }
249
+ if (flags.open && flags.open !== "exit") {
250
+ const url = destinationUrls2[flags.open];
251
+ if (url) {
252
+ await safeOpen(url);
253
+ process.exit(0);
254
+ }
255
+ }
256
+ if (!isInteractive) {
257
+ console.log("\nQuick links:");
258
+ console.log(`- Portfolio: ${personalConfig.portfolio}`);
259
+ console.log(`- GitHub: ${personalConfig.links.github}`);
260
+ console.log(`- LinkedIn: ${personalConfig.links.linkedin}`);
261
+ console.log(`- Peerlist: ${personalConfig.links.peerlist}`);
262
+ console.log(`- Twitter: ${personalConfig.links.twitter}
263
+ `);
264
+ process.exit(0);
265
+ }
266
+ process.on("SIGTERM", () => {
267
+ console.log("\u270B Received SIGTERM. Bye!");
268
+ process.exit(0);
269
+ });
270
+ process.on("SIGINT", () => {
271
+ console.log("\n\u270B Caught Ctrl+C. Take care and have a great day!");
272
+ process.exit(0);
273
+ });
274
+ try {
275
+ await showBanner(flags.noAnim || isCI);
276
+ showCard();
277
+ await showMenu();
278
+ } catch (error) {
279
+ if (error instanceof Error && error.message?.includes("SIGINT")) {
280
+ console.log("\u270B Caught Ctrl+C. Take care and have a great day!");
281
+ process.exitCode = 0;
282
+ } else {
283
+ console.log("Prompt closed. Until next time! \u{1F44B}");
284
+ process.exitCode = 0;
285
+ }
286
+ }
287
+ }
288
+ main().catch((error) => {
289
+ console.error("An error occurred:", error);
290
+ process.exit(1);
291
+ });
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "himanshuchandola",
3
+ "version": "1.0.0",
4
+ "description": "Interactive CLI portfolio for Himanshu Chandola - A delightful terminal experience",
5
+ "type": "module",
6
+ "bin": {
7
+ "himanshuchandola": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "dev": "tsup --watch",
17
+ "build": "tsup",
18
+ "start": "node dist/index.js",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "test:coverage": "vitest run --coverage",
22
+ "type-check": "tsc --noEmit",
23
+ "lint": "biome check .",
24
+ "lint:fix": "biome check --write .",
25
+ "format": "biome format --write .",
26
+ "prepare": "husky",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "keywords": [
30
+ "cli",
31
+ "terminal",
32
+ "interactive",
33
+ "portfolio",
34
+ "developer-portfolio",
35
+ "himanshuchandola",
36
+ "cli-app"
37
+ ],
38
+ "author": {
39
+ "name": "Himanshu Chandola",
40
+ "url": "https://himanshuchandola.dev"
41
+ },
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/himanshuchandola/himanshuchandola-cli.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/himanshuchandola/himanshuchandola-cli/issues"
49
+ },
50
+ "homepage": "https://github.com/himanshuchandola/himanshuchandola-cli#readme",
51
+ "engines": {
52
+ "node": ">=18"
53
+ },
54
+ "dependencies": {
55
+ "boxen": "^8.0.1",
56
+ "chalk": "^5.6.2",
57
+ "cli-spinners": "^3.3.0",
58
+ "figlet": "^1.9.4",
59
+ "inquirer": "^13.1.0",
60
+ "open": "^11.0.0",
61
+ "ora": "^9.0.0"
62
+ },
63
+ "devDependencies": {
64
+ "@biomejs/biome": "^2.3.11",
65
+ "@commitlint/cli": "^20.3.0",
66
+ "@commitlint/config-conventional": "^20.3.0",
67
+ "@semantic-release/changelog": "^6.0.3",
68
+ "@semantic-release/git": "^10.0.1",
69
+ "@semantic-release/github": "^12.0.2",
70
+ "@types/figlet": "^1.7.0",
71
+ "@types/inquirer": "^9.0.9",
72
+ "@types/node": "^25.0.3",
73
+ "@vitest/coverage-v8": "^4.0.16",
74
+ "husky": "^9.1.7",
75
+ "semantic-release": "^25.0.2",
76
+ "tsup": "^8.5.1",
77
+ "typescript": "^5.9.3",
78
+ "vitest": "^4.0.16"
79
+ }
80
+ }