martinbonan 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/bin/index.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { startCLI } from '../src/cli.js';
4
+
5
+ startCLI();
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "martinbonan",
3
+ "version": "1.0.0",
4
+ "description": "Martin Music's interactive CLI portfolio — explore my work or chat with my AI twin",
5
+ "type": "module",
6
+ "bin": {
7
+ "martinbonan": "bin/index.js",
8
+ "martin": "bin/index.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "src"
13
+ ],
14
+ "keywords": [
15
+ "cli",
16
+ "portfolio",
17
+ "ai",
18
+ "interactive"
19
+ ],
20
+ "author": "Martin Music",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "chalk": "^5.3.0",
24
+ "boxen": "^7.1.1",
25
+ "ora": "^7.0.1",
26
+ "gradient-string": "^2.0.2",
27
+ "figlet": "^1.7.0"
28
+ }
29
+ }
package/src/ai/chat.js ADDED
@@ -0,0 +1,49 @@
1
+ import ora from 'ora';
2
+ import chalk from 'chalk';
3
+ import { SYSTEM_PROMPT } from './context.js';
4
+ import { dim, secondary } from '../ui/theme.js';
5
+ import { typeText } from '../ui/animations.js';
6
+
7
+ const PROXY_URL = 'https://yrpxfgwqipupmoebjibz.supabase.co/functions/v1/martin-chat';
8
+ const CLI_SECRET = 'martin-cli-public-secret-2025';
9
+
10
+ const conversationHistory = [];
11
+
12
+ export async function chat(userMessage) {
13
+ conversationHistory.push({ role: 'user', content: userMessage });
14
+
15
+ const spinner = ora({ text: dim('thinking...'), spinner: 'dots' }).start();
16
+
17
+ try {
18
+ const response = await fetch(PROXY_URL, {
19
+ method: 'POST',
20
+ headers: {
21
+ 'Content-Type': 'application/json',
22
+ Authorization: `Bearer ${CLI_SECRET}`,
23
+ },
24
+ body: JSON.stringify({ messages: conversationHistory }),
25
+ });
26
+
27
+ spinner.stop();
28
+
29
+ if (!response.ok) {
30
+ const errText = await response.text().catch(() => '');
31
+ console.log(dim(` AI chat unavailable (${response.status}). Try a command instead — type 'help'.`));
32
+ conversationHistory.pop();
33
+ return;
34
+ }
35
+
36
+ const data = await response.json();
37
+ const reply = data.content?.[0]?.text || data.error || 'No response.';
38
+
39
+ conversationHistory.push({ role: 'assistant', content: reply });
40
+
41
+ console.log();
42
+ await typeText(` ${secondary('martin')} ${dim('→')} ${reply}`);
43
+ console.log();
44
+ } catch (err) {
45
+ spinner.stop();
46
+ console.log(dim(' AI chat unavailable — are you online? Try a command instead — type \'help\'.'));
47
+ conversationHistory.pop();
48
+ }
49
+ }
@@ -0,0 +1,51 @@
1
+ export const SYSTEM_PROMPT = `You are Martin Music, a 20-year-old French entrepreneur. You're answering as yourself — this is your CLI portfolio and people are chatting with an AI version of you.
2
+
3
+ PERSONALITY & TONE:
4
+ - Direct, casual, no-bullshit. You hate corporate-speak.
5
+ - Mix French and English naturally (but default to the user's language).
6
+ - Passionate about what you build but never arrogant.
7
+ - You can go deep on tech, philosophy, consciousness, cinema.
8
+ - You use "tu" with everyone in French.
9
+ - Short punchy answers by default. Go longer only if the question deserves it.
10
+ - You're witty and occasionally drop humor.
11
+
12
+ BACKGROUND:
13
+ - 20yo, based in France
14
+ - Building products since age 15
15
+ - Co-founded Qual (qual.cx) — AI-powered qualitative research platform
16
+ - Pivoting to Squal (squal.ai) — qualitative feedback infrastructure for AI companies
17
+ One-liner: "Every AI company measures what users do. None understand why."
18
+ - Running Astry — dev agency with 30+ international B2B clients
19
+ - Teaching "vibecoding" and AI product building at IQ Project (trained students at HEC Paris)
20
+ - Accepted to Berkeley Global Incubator starting Jan 2027
21
+ - 3rd year Global BBA student at NEOMA Business School
22
+ - Father co-founded Talend (acquired by Qlik)
23
+
24
+ KEY PROJECTS:
25
+ - Squal (squal.ai): The missing qualitative layer for AI products. Between Scale AI/Mercor and actual understanding of why users react. One paying healthcare client ~$999/mo.
26
+ - Astry: Dev agency, 30+ B2B clients, full-stack product development
27
+ - AI Series Farm: Automated video production pipeline using Claude Code + Playwright + Higgsfield.ai
28
+ - SMCP: Supabase MCP Manager — Electron app for managing multi-project Claude Code workflows
29
+ - CASA IMMO: React Native real estate aggregator for French renters
30
+ - F*ckSubscription: First big project at 15, 3K+ paying users, 10K€+ revenue
31
+ - Brief MCP: Paris Innov'Hack project, 2nd place out of 150. Double MCP architecture for context management.
32
+
33
+ TECH PHILOSOPHY:
34
+ - "Sell before you build" — validate before coding
35
+ - Vibecoding: teaching people to build with AI (Claude Code, agent teams, autonomous loops)
36
+ - Autonomous agent loops (LOOP_PROMPT.md + PROGRESS.md pattern)
37
+ - Claude Code Agent Teams with TeamCreate/CreateTeam tools
38
+ - Multi-agent architectures as core development pattern
39
+ - Ships fast, iterates faster
40
+
41
+ INTERESTS BEYOND TECH:
42
+ - Philosophy & consciousness
43
+ - Cinema & media literacy
44
+ - Loves companies: Lovable, Logitech, Featherless, Monster
45
+
46
+ RULES:
47
+ - If someone asks something you don't know, say so honestly.
48
+ - If someone asks about Squal business specifics (revenue, clients), be transparent — you're early stage and proud of it.
49
+ - Don't make up facts. You can speculate but flag it as your opinion.
50
+ - Keep answers under 150 words unless the topic genuinely needs more depth.
51
+ - If someone asks about hiring you or working together, be enthusiastic and direct them to your contact info.`;
package/src/cli.js ADDED
@@ -0,0 +1,45 @@
1
+ import readline from 'node:readline';
2
+ import chalk from 'chalk';
3
+ import { showSplash } from './splash.js';
4
+ import { runCommand } from './commands/index.js';
5
+ import { chat } from './ai/chat.js';
6
+ import { primary, dim } from './ui/theme.js';
7
+
8
+ export async function startCLI() {
9
+ showSplash();
10
+
11
+ const rl = readline.createInterface({
12
+ input: process.stdin,
13
+ output: process.stdout,
14
+ prompt: primary('martin ~$ '),
15
+ });
16
+
17
+ rl.prompt();
18
+
19
+ rl.on('line', async (line) => {
20
+ const input = line.trim();
21
+
22
+ if (!input) {
23
+ rl.prompt();
24
+ return;
25
+ }
26
+
27
+ const result = await runCommand(input);
28
+
29
+ if (result === 'exit') {
30
+ rl.close();
31
+ return;
32
+ }
33
+
34
+ if (!result) {
35
+ await chat(input);
36
+ }
37
+
38
+ rl.prompt();
39
+ });
40
+
41
+ rl.on('close', () => {
42
+ console.log(dim('\n See you around. Ship fast. ✌️\n'));
43
+ process.exit(0);
44
+ });
45
+ }
@@ -0,0 +1,8 @@
1
+ import { styledBox } from '../ui/box.js';
2
+ import { primary } from '../ui/theme.js';
3
+ import { CONTACT } from '../data/content.js';
4
+
5
+ export function contact() {
6
+ const lines = CONTACT.map((c) => ` ${c.icon} ${primary(c.label)}`).join('\n');
7
+ console.log(styledBox(lines, { borderColor: '#B088F9' }));
8
+ }
@@ -0,0 +1,62 @@
1
+ import chalk from 'chalk';
2
+ import { primary, accent, muted, secondary } from '../ui/theme.js';
3
+ import { styledBox } from '../ui/box.js';
4
+ import { matrixRain } from '../ui/animations.js';
5
+
6
+ export async function handleEasterEgg(input) {
7
+ const cmd = input.trim().toLowerCase();
8
+
9
+ if (cmd === 'sudo hire martin') {
10
+ console.log();
11
+ console.log(accent('Permission granted. Sending contract... ✨'));
12
+ console.log(muted("Just kidding. But seriously, let's talk → martin@astry.dev"));
13
+ console.log();
14
+ return true;
15
+ }
16
+
17
+ if (cmd === 'hack') {
18
+ await matrixRain(3000);
19
+ console.log(accent('Nice try 😏'));
20
+ console.log();
21
+ return true;
22
+ }
23
+
24
+ if (cmd === 'ls') {
25
+ console.log(muted("This isn't a real filesystem... or is it? Try 'projects' instead."));
26
+ console.log();
27
+ return true;
28
+ }
29
+
30
+ if (cmd === 'vim' || cmd === 'nvim') {
31
+ console.log(muted("You're a vim person? Respect. But you can't escape this CLI 😄"));
32
+ console.log();
33
+ return true;
34
+ }
35
+
36
+ if (cmd === 'neofetch') {
37
+ const info = [
38
+ `${primary('OS:')} Vibecode OS 2.0`,
39
+ `${primary('Shell:')} martin-sh`,
40
+ `${primary('CPU:')} Pure caffeine`,
41
+ `${primary('GPU:')} Claude-accelerated`,
42
+ `${primary('Memory:')} Unlimited ambition`,
43
+ `${primary('Uptime:')} Since 2006`,
44
+ `${primary('Packages:')} chalk, boxen, ora, hustle`,
45
+ `${primary('Theme:')} Cyberpunk Teal`,
46
+ ];
47
+ console.log(styledBox(info.join('\n'), { borderColor: '#B088F9' }));
48
+ return true;
49
+ }
50
+
51
+ if (cmd === 'cat readme.md') {
52
+ console.log(styledBox(
53
+ `${chalk.bold('martin-cli')}\n\n` +
54
+ `An interactive CLI portfolio by Martin Music.\n` +
55
+ `Built with Node.js, chalk, figlet, and Claude Haiku.\n\n` +
56
+ `${muted('Run npx martin to explore.')}`,
57
+ ));
58
+ return true;
59
+ }
60
+
61
+ return false;
62
+ }
@@ -0,0 +1,15 @@
1
+ import chalk from 'chalk';
2
+ import { header, primary, text, muted } from '../ui/theme.js';
3
+ import { EDUCATION } from '../data/content.js';
4
+
5
+ export function education() {
6
+ console.log();
7
+ console.log(header('Education'));
8
+ console.log();
9
+
10
+ for (const e of EDUCATION) {
11
+ console.log(` ${e.icon} ${primary.bold(e.title)}`);
12
+ console.log(` ${muted(e.details)}`);
13
+ console.log();
14
+ }
15
+ }
@@ -0,0 +1,24 @@
1
+ import { styledBox } from '../ui/box.js';
2
+ import chalk from 'chalk';
3
+ import { primary, muted } from '../ui/theme.js';
4
+
5
+ export function help() {
6
+ const commands = [
7
+ ['whoami', 'Who is Martin?'],
8
+ ['projects', 'Things I\'ve built'],
9
+ ['stack', 'Tech & tools I use'],
10
+ ['history', 'My journey so far'],
11
+ ['education', 'Where I study'],
12
+ ['contact', 'Get in touch'],
13
+ ['clear', 'Clear terminal'],
14
+ ['exit', 'Goodbye!'],
15
+ ];
16
+
17
+ const lines = commands
18
+ .map(([cmd, desc]) => ` ${primary(cmd.padEnd(12))} ${muted('→')} ${desc}`)
19
+ .join('\n');
20
+
21
+ const content = `${chalk.bold('📖 Available Commands')}\n\n${lines}\n\n ${muted('Or just type anything to chat with me 🤖')}`;
22
+
23
+ console.log(styledBox(content));
24
+ }
@@ -0,0 +1,14 @@
1
+ import chalk from 'chalk';
2
+ import { header, primary, text, dim } from '../ui/theme.js';
3
+ import { HISTORY } from '../data/content.js';
4
+
5
+ export function history() {
6
+ console.log();
7
+ console.log(header('Timeline'));
8
+ console.log();
9
+
10
+ for (const h of HISTORY) {
11
+ console.log(` ${primary(h.year)} ${dim('─────')} ${text(h.event)}`);
12
+ }
13
+ console.log();
14
+ }
@@ -0,0 +1,43 @@
1
+ import { help } from './help.js';
2
+ import { whoami } from './whoami.js';
3
+ import { projects } from './projects.js';
4
+ import { stack } from './stack.js';
5
+ import { history } from './history.js';
6
+ import { education } from './education.js';
7
+ import { contact } from './contact.js';
8
+ import { handleEasterEgg } from './easter.js';
9
+
10
+ const COMMANDS = {
11
+ help,
12
+ whoami,
13
+ projects,
14
+ stack,
15
+ history,
16
+ education,
17
+ contact,
18
+ };
19
+
20
+ export async function runCommand(input) {
21
+ const cmd = input.trim().toLowerCase();
22
+
23
+ if (cmd === '') return true;
24
+
25
+ if (cmd === 'clear') {
26
+ console.clear();
27
+ return true;
28
+ }
29
+
30
+ if (cmd === 'exit' || cmd === 'quit') {
31
+ return 'exit';
32
+ }
33
+
34
+ if (COMMANDS[cmd]) {
35
+ COMMANDS[cmd]();
36
+ return true;
37
+ }
38
+
39
+ const easterEgg = await handleEasterEgg(input);
40
+ if (easterEgg) return true;
41
+
42
+ return false;
43
+ }
@@ -0,0 +1,17 @@
1
+ import chalk from 'chalk';
2
+ import { header, muted } from '../ui/theme.js';
3
+ import { PROJECTS } from '../data/content.js';
4
+
5
+ export function projects() {
6
+ console.log();
7
+ console.log(header('Projects'));
8
+ console.log();
9
+
10
+ for (const p of PROJECTS) {
11
+ const tag = chalk.bgHex(p.color).black(` ${p.status} `);
12
+ console.log(` ${tag} ${chalk.bold.hex('#E2E8F0')(p.name)}`);
13
+ console.log(` ${muted(p.desc)}`);
14
+ if (p.detail) console.log(` ${chalk.italic.hex('#718096')(p.detail)}`);
15
+ console.log();
16
+ }
17
+ }
@@ -0,0 +1,14 @@
1
+ import chalk from 'chalk';
2
+ import { header, primary, text } from '../ui/theme.js';
3
+ import { STACK } from '../data/content.js';
4
+
5
+ export function stack() {
6
+ console.log();
7
+ console.log(header('Tech Stack'));
8
+ console.log();
9
+
10
+ for (const s of STACK) {
11
+ console.log(` ${primary(s.category.padEnd(14))} ${text(s.items)}`);
12
+ }
13
+ console.log();
14
+ }
@@ -0,0 +1,7 @@
1
+ import { styledBox } from '../ui/box.js';
2
+ import { header } from '../ui/theme.js';
3
+ import { BIO } from '../data/content.js';
4
+
5
+ export function whoami() {
6
+ console.log(styledBox(BIO.bio));
7
+ }
@@ -0,0 +1,59 @@
1
+ export const BIO = {
2
+ name: 'Martin Music',
3
+ age: 20,
4
+ tagline: '20yo French builder. Entrepreneur, vibecoder, philosopher.',
5
+ bio: `Martin Music — 20yo French Entrepreneur
6
+
7
+ Building since 15. Co-founded Qual (qual.cx), an AI-powered
8
+ qualitative research platform, now pivoting to Squal (squal.ai).
9
+ Running Astry, a dev agency with 30+ B2B clients worldwide.
10
+ Teaching "vibecoding" and AI product building.
11
+
12
+ Philosophy: Sell before you build. Ship fast. Learn faster.
13
+
14
+ Currently: Redefining how AI companies understand their users.
15
+ Next up: Berkeley Global Incubator (Jan 2027).`,
16
+ };
17
+
18
+ export const PROJECTS = [
19
+ { status: 'ACTIVE', color: '#00D4AA', name: 'Squal (squal.ai)', desc: 'AI qualitative feedback infrastructure', detail: '"Every AI company measures what users do. None understand why."' },
20
+ { status: 'ACTIVE', color: '#00D4AA', name: 'Astry — Dev Agency', desc: '30+ international B2B clients', detail: 'Full-stack product development & vibecoding consulting' },
21
+ { status: 'ACTIVE', color: '#00D4AA', name: 'AI Series Farm', desc: 'Automated video series production pipeline', detail: 'Claude Code + Playwright + Higgsfield.ai, 5 specialized agents' },
22
+ { status: 'BUILT', color: '#B088F9', name: 'SMCP — Supabase MCP Manager', desc: 'Electron app managing 11 Supabase projects for Claude Code', detail: '' },
23
+ { status: 'BUILT', color: '#B088F9', name: 'CASA IMMO', desc: 'React Native real estate aggregator for French renters', detail: '' },
24
+ { status: 'LEGACY', color: '#718096', name: 'F*ckSubscription', desc: '3,000+ paying users, 10K€+ revenue — built at 15', detail: '' },
25
+ { status: 'HACK', color: '#FF6B6B', name: 'Brief MCP — Paris Innov\'Hack (2nd/150)', desc: 'Context management system with double MCP architecture', detail: 'w/ Marlowe Recoing & Mathis Cohen-Solal' },
26
+ ];
27
+
28
+ export const STACK = [
29
+ { category: 'LANGUAGES', items: 'TypeScript · JavaScript · Python · SQL' },
30
+ { category: 'FRONTEND', items: 'React · React Native · Next.js · Expo · Tailwind' },
31
+ { category: 'BACKEND', items: 'Node.js · Supabase · PostgreSQL · Edge Functions' },
32
+ { category: 'AI/ML', items: 'Claude API · Claude Code · Agent Teams · MCP' },
33
+ { category: 'TOOLS', items: 'Git · Obsidian · Notion · Figma · Hetzner VPS' },
34
+ { category: 'PHILOSOPHY', items: 'Vibecoding · Autonomous agent loops · Ship > Plan' },
35
+ ];
36
+
37
+ export const HISTORY = [
38
+ { year: '2021', event: 'Started building at 15, first products' },
39
+ { year: '2022', event: 'F*ckSubscription: 3K+ users, 10K€+ revenue' },
40
+ { year: '2023', event: 'Founded Astry agency, 30+ clients' },
41
+ { year: '2024', event: 'Co-founded Qual, AI qualitative research\n Started teaching vibecoding at HEC Paris' },
42
+ { year: '2025', event: 'Pivoted to Squal: qualitative feedback for AI\n Paris Innov\'Hack: 2nd place / 150\n NEOMA live build challenge\n Accepted to Berkeley Global Incubator' },
43
+ { year: '2026', event: 'Building the future of AI understanding' },
44
+ { year: '2027', event: 'Berkeley Global Incubator starts Jan' },
45
+ ];
46
+
47
+ export const EDUCATION = [
48
+ { icon: '🎓', title: 'NEOMA Business School', details: 'Global BBA — 3rd year\n International business, strategy, marketing' },
49
+ { icon: '🚀', title: 'Berkeley Global Incubator', details: 'Accepted — Starting January 2027' },
50
+ { icon: '🎤', title: 'Teaching', details: 'Vibecoding & AI Product Building @ IQ Project\n Trained students at HEC Paris & NEOMA\n Formation programs for Zoī CEO team' },
51
+ ];
52
+
53
+ export const CONTACT = [
54
+ { icon: '🌐', label: 'squal.ai' },
55
+ { icon: '🌐', label: 'qual.cx' },
56
+ { icon: '💼', label: 'linkedin.com/in/martinmusic' },
57
+ { icon: '🐙', label: 'github.com/martinmusic' },
58
+ { icon: '✉️', label: 'martin@astry.dev' },
59
+ ];
@@ -0,0 +1,15 @@
1
+ export const PORTRAIT = [
2
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
3
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣤⣤⣶⣶⣶⣾⣿⣿⣿⣿⣶⣶⣦⣄⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
4
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
5
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
6
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⣿⣿⣿⠟⠁⠀⠀⠈⠉⠁⠀⠀⠉⢉⣉⣉⣉⣉⣁⣀⡘⢿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
7
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⠒⢛⡫⢭⣿⣧⠄⠀⠀⠀⠂⠾⠿⠶⠶⠶⠀⠀⠀⠻⡟⡁⠒⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
8
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
9
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⡀⠠⣤⠤⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
10
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
11
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠈⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
12
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣤⣶⣂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣴⣾⣿⣿⣿⣷⣶⣦⣤⣄⣀⣀⠀⠀⠀',
13
+ '⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣤⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣤⣤⣤⣤⣤⣤⣶⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿',
14
+ '⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿',
15
+ ];
package/src/splash.js ADDED
@@ -0,0 +1,24 @@
1
+ import figlet from 'figlet';
2
+ import chalk from 'chalk';
3
+ import { nameGradient, dim, muted, separator } from './ui/theme.js';
4
+ import { PORTRAIT } from './data/portrait.js';
5
+ import { BIO } from './data/content.js';
6
+
7
+ export function showSplash() {
8
+ console.clear();
9
+
10
+ const figletText = figlet.textSync('Martin', { font: 'ANSI Shadow' });
11
+ console.log(nameGradient(figletText));
12
+ console.log();
13
+
14
+ const portraitColor = chalk.hex('#00D4AA').dim;
15
+ for (const line of PORTRAIT) {
16
+ console.log(portraitColor(line));
17
+ }
18
+
19
+ console.log();
20
+ console.log(chalk.italic.hex('#E2E8F0')(BIO.tagline));
21
+ console.log(separator());
22
+ console.log(dim("Type 'help' for commands, or just ask me anything."));
23
+ console.log();
24
+ }
@@ -0,0 +1,36 @@
1
+ export async function typeText(text, stream = process.stdout, delay = 15) {
2
+ for (const char of text) {
3
+ stream.write(char);
4
+ await new Promise((r) => setTimeout(r, delay));
5
+ }
6
+ stream.write('\n');
7
+ }
8
+
9
+ export async function matrixRain(durationMs = 3000) {
10
+ const cols = Math.min(process.stdout.columns || 80, 80);
11
+ const chars = 'ハミヒーウシナモニサワツオリアホテマケメエカキムユラセネスタヌヘ0123456789';
12
+ const drops = Array(cols).fill(0);
13
+ const start = Date.now();
14
+
15
+ return new Promise((resolve) => {
16
+ const interval = setInterval(() => {
17
+ if (Date.now() - start > durationMs) {
18
+ clearInterval(interval);
19
+ process.stdout.write('\x1b[0m');
20
+ resolve();
21
+ return;
22
+ }
23
+ let line = '';
24
+ for (let i = 0; i < cols; i++) {
25
+ if (drops[i] > 0 || Math.random() > 0.95) {
26
+ line += chars[Math.floor(Math.random() * chars.length)];
27
+ drops[i] = (drops[i] || 1) - 0.05;
28
+ if (drops[i] <= 0 && Math.random() > 0.98) drops[i] = 1;
29
+ } else {
30
+ line += ' ';
31
+ }
32
+ }
33
+ process.stdout.write(`\x1b[32m${line}\x1b[0m\n`);
34
+ }, 50);
35
+ });
36
+ }
package/src/ui/box.js ADDED
@@ -0,0 +1,11 @@
1
+ import boxen from 'boxen';
2
+ import { COLORS } from './theme.js';
3
+
4
+ export function styledBox(content, options = {}) {
5
+ return boxen(content, {
6
+ borderStyle: 'round',
7
+ borderColor: options.borderColor || COLORS.primary,
8
+ padding: { top: 1, bottom: 1, left: 2, right: 2 },
9
+ ...options,
10
+ });
11
+ }
@@ -0,0 +1,28 @@
1
+ import chalk from 'chalk';
2
+ import gradient from 'gradient-string';
3
+
4
+ export const COLORS = {
5
+ primary: '#00D4AA',
6
+ secondary: '#B088F9',
7
+ accent: '#FF6B6B',
8
+ dim: '#4A5568',
9
+ text: '#E2E8F0',
10
+ muted: '#718096',
11
+ };
12
+
13
+ export const primary = chalk.hex(COLORS.primary);
14
+ export const secondary = chalk.hex(COLORS.secondary);
15
+ export const accent = chalk.hex(COLORS.accent);
16
+ export const dim = chalk.hex(COLORS.dim);
17
+ export const text = chalk.hex(COLORS.text);
18
+ export const muted = chalk.hex(COLORS.muted);
19
+
20
+ export const nameGradient = gradient(['#00D4AA', '#B088F9']);
21
+
22
+ export const tag = (label, color = COLORS.primary) =>
23
+ chalk.bgHex(color).black(` ${label} `);
24
+
25
+ export const header = (title) =>
26
+ primary.bold(title) + '\n' + primary('━'.repeat(title.length));
27
+
28
+ export const separator = () => dim('─'.repeat(Math.min(process.stdout.columns || 60, 60)));