brignano 1.0.0 → 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/src/menu.ts ADDED
@@ -0,0 +1,84 @@
1
+ import chalk from 'chalk';
2
+ import clipboard from 'clipboardy';
3
+ import open from 'open';
4
+ import { profile } from './data.js';
5
+ import { renderQr } from './qrcode.js';
6
+ import { buildVCard, writeAndOpenVCard } from './vcard.js';
7
+
8
+ // --- Action helpers (kept as named functions so flags and the menu share them) ---
9
+
10
+ export async function copyEmail(): Promise<void> {
11
+ try {
12
+ await clipboard.write(profile.email);
13
+ console.log(`\n${chalk.green('Copied')} ${profile.email} to your clipboard.\n`);
14
+ } catch {
15
+ // Clipboard access fails in headless/SSH environments — degrade gracefully.
16
+ console.log(`\nMy email is ${chalk.green(profile.email)} — drop me a line!\n`);
17
+ }
18
+ }
19
+
20
+ export async function sendEmail(): Promise<void> {
21
+ try {
22
+ await open(`mailto:${profile.email}`);
23
+ console.log('\nOpened your mail client — see you in the inbox.\n');
24
+ } catch (err) {
25
+ const message = err instanceof Error ? err.message : String(err);
26
+ console.error(`Failed to open mail client: ${message}`);
27
+ console.error(`Reach me directly at ${profile.email}`);
28
+ }
29
+ }
30
+
31
+ export async function openUrl(url: string, label: string): Promise<void> {
32
+ try {
33
+ await open(url);
34
+ console.log(`\nOpened ${label}: ${url}\n`);
35
+ } catch (err) {
36
+ const message = err instanceof Error ? err.message : String(err);
37
+ console.error(`Failed to open ${label}: ${message}`);
38
+ console.error(`Here it is: ${url}`);
39
+ }
40
+ }
41
+
42
+ export async function viewResume(): Promise<void> {
43
+ await openUrl(profile.resume, 'resume');
44
+ }
45
+
46
+ export async function showContactQr(): Promise<void> {
47
+ const qr = await renderQr(buildVCard());
48
+ console.log('\nScan to save my contact:\n');
49
+ console.log(qr);
50
+ }
51
+
52
+ // --- Menu definition ---
53
+
54
+ export interface MenuChoice {
55
+ name: string;
56
+ value: () => void | Promise<void>;
57
+ }
58
+
59
+ export function buildChoices(): MenuChoice[] {
60
+ return [
61
+ { name: `Copy my ${chalk.green.bold('email')} to clipboard`, value: copyEmail },
62
+ { name: `Send me an ${chalk.green.bold('email')}`, value: sendEmail },
63
+ {
64
+ name: `Open my ${chalk.green.bold('GitHub')}`,
65
+ value: () => openUrl(profile.github, 'GitHub'),
66
+ },
67
+ {
68
+ name: `Open my ${chalk.blue.bold('LinkedIn')}`,
69
+ value: () => openUrl(profile.linkedin, 'LinkedIn'),
70
+ },
71
+ { name: `View my ${chalk.magentaBright.bold('resume')}`, value: viewResume },
72
+ {
73
+ name: `Save my ${chalk.cyan.bold('contact')} (.vcf)`,
74
+ value: writeAndOpenVCard,
75
+ },
76
+ { name: `Show a ${chalk.yellow.bold('contact QR')} code`, value: showContactQr },
77
+ {
78
+ name: 'Just quit',
79
+ value: () => {
80
+ console.log('\nHave a great day!\n');
81
+ },
82
+ },
83
+ ];
84
+ }
package/src/qrcode.ts ADDED
@@ -0,0 +1,9 @@
1
+ import qrcode from 'qrcode-terminal';
2
+
3
+ // Promisified wrapper around qrcode-terminal's callback API. `small: true` keeps
4
+ // the code compact enough for most terminals.
5
+ export function renderQr(text: string): Promise<string> {
6
+ return new Promise((resolve) => {
7
+ qrcode.generate(text, { small: true }, (output: string) => resolve(output));
8
+ });
9
+ }
package/src/reveal.ts ADDED
@@ -0,0 +1,25 @@
1
+ // Tiny animation helpers — no dependencies. Line-by-line reveal is immune to the
2
+ // ANSI-escape-splitting problem a per-character typewriter would hit, and the
3
+ // total budget stays well under a second.
4
+
5
+ export function sleep(ms: number): Promise<void> {
6
+ return new Promise((resolve) => setTimeout(resolve, ms));
7
+ }
8
+
9
+ // Prints `text` one line at a time, pausing `perLineMs` between lines.
10
+ // When `animate` is false, prints everything at once (CI / piped / --no-animation).
11
+ export async function reveal(
12
+ text: string,
13
+ perLineMs = 35,
14
+ animate = true,
15
+ ): Promise<void> {
16
+ if (!animate) {
17
+ console.log(text);
18
+ return;
19
+ }
20
+ const lines = text.split('\n');
21
+ for (const line of lines) {
22
+ console.log(line);
23
+ await sleep(perLineMs);
24
+ }
25
+ }
@@ -0,0 +1,13 @@
1
+ // qrcode-terminal ships no official types and is CJS. Minimal ambient shim.
2
+ declare module 'qrcode-terminal' {
3
+ interface GenerateOptions {
4
+ small?: boolean;
5
+ }
6
+ function generate(
7
+ input: string,
8
+ options?: GenerateOptions,
9
+ callback?: (output: string) => void,
10
+ ): void;
11
+ const qrcode: { generate: typeof generate };
12
+ export default qrcode;
13
+ }
package/src/vcard.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import open from 'open';
5
+ import { profile } from './data.js';
6
+
7
+ // Builds a valid vCard 3.0 string (broadest contact-app compatibility) with the
8
+ // spec-required CRLF line endings.
9
+ export function buildVCard(): string {
10
+ const lines = [
11
+ 'BEGIN:VCARD',
12
+ 'VERSION:3.0',
13
+ `N:${profile.lastName};${profile.firstName};;;`,
14
+ `FN:${profile.fullName}`,
15
+ `ORG:${profile.org}`,
16
+ `TITLE:${profile.title}`,
17
+ `EMAIL;TYPE=INTERNET,PREF:${profile.email}`,
18
+ `URL:${profile.website}`,
19
+ `X-SOCIALPROFILE;TYPE=github:${profile.github}`,
20
+ `X-SOCIALPROFILE;TYPE=linkedin:${profile.linkedin}`,
21
+ 'NOTE:npx brignano',
22
+ 'END:VCARD',
23
+ ];
24
+ return lines.join('\r\n') + '\r\n';
25
+ }
26
+
27
+ // Writes the vCard to a temp file and asks the OS to open it (Contacts on
28
+ // macOS/iOS, the default handler elsewhere). On failure, prints the path so the
29
+ // user can open it manually — never throws.
30
+ export async function writeAndOpenVCard(): Promise<void> {
31
+ const file = join(tmpdir(), 'anthony-brignano.vcf');
32
+ try {
33
+ writeFileSync(file, buildVCard(), 'utf8');
34
+ await open(file);
35
+ console.log(`\nSaved my contact card to: ${file}\n`);
36
+ } catch (err) {
37
+ const message = err instanceof Error ? err.message : String(err);
38
+ console.error(`\nCouldn't open the contact card automatically (${message}).`);
39
+ console.error(`It's saved here — open it manually: ${file}\n`);
40
+ }
41
+ }
package/vhs/demo.tape ADDED
@@ -0,0 +1,40 @@
1
+ # Demo recording for `npx brignano`.
2
+ # Regenerate with: vhs vhs/demo.tape (requires: brew install vhs)
3
+ # Renders the gradient banner, the live card, menu navigation, and a contact QR.
4
+
5
+ Output demo.gif
6
+ Require node
7
+
8
+ Set Shell "zsh"
9
+ Set FontSize 14
10
+ Set Width 1000
11
+ Set Height 1000
12
+ Set Padding 20
13
+ Set Theme "GitHub Dark"
14
+ Set TypingSpeed 55ms
15
+
16
+ # Launch the local build so the demo reflects this branch (not the published npm version).
17
+ Type "node dist/card.js"
18
+ Sleep 400ms
19
+ Enter
20
+
21
+ # Banner animation + card reveal + live GitHub stats.
22
+ Sleep 4s
23
+
24
+ # Walk down the menu to showcase the actions.
25
+ Down
26
+ Sleep 600ms
27
+ Down
28
+ Sleep 600ms
29
+ Down
30
+ Sleep 600ms
31
+ Down
32
+ Sleep 600ms
33
+ Down
34
+ Sleep 600ms
35
+ Down
36
+ Sleep 800ms
37
+
38
+ # Land on "Show a contact QR code" and select it.
39
+ Enter
40
+ Sleep 3s