jobless 1.1.0 → 1.2.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 CHANGED
@@ -4,63 +4,86 @@
4
4
  npx jobless
5
5
  ```
6
6
 
7
- A terminal resume for a broken job market. LinkedIn is a content farm with a jobs tab. Applications take 30 minutes. The no takes 400ms. Recruiters book the call so they have something to cancel. I still do not know their coffee, their mug, or their star sign.
7
+ A terminal resume for **Yonas Alem**, senior software engineer. Remote. Production systems across payments, gaming, ecommerce, and AWS.
8
8
 
9
- ## The market
9
+ The joke is optional. The work is the point.
10
10
 
11
- ### I do not know their coffee, their mug, or their stars
11
+ ## What you get in 10 seconds
12
12
 
13
- The job posting said TypeScript, PostgreSQL, and "excellent communication." The actual exam is telepathy. What is on the recruiter's mind at 10:02am. Oat milk or "just black, I ship." What color is the mug. Company merch that says world-class talent, or the chipped navy one from a 2019 conference that still has a lipstick ghost. What is their star sign. A Virgo who will reject you for a missing Oxford comma. A Gemini who double-booked you with someone named also-Yonas. A Scorpio who already decided during the handshake.
13
+ ```
14
+ Yonas Alem
15
+ Senior Software Engineer
16
+ Addis Ababa, Ethiopia · Open to remote senior roles
14
17
 
15
- I can trace a silent cron death across 17 processes. I cannot see into their mug. Apparently that was the seniority gap. Next round I will bring a natal chart, a color swatch, and a cortado. Or we could talk about the actual system.
18
+ 5+ years in production 35% infra cost cut
19
+ ~30% performance gain 3B+ ETB payment volume
16
20
 
17
- ### I applied for 30 minutes. The bot needed 400ms
21
+ PROOF
22
+ The deploy that killed 17 cron jobs
23
+ • Recurring Amazon sync jobs silently stopped after every deploy.
24
+ • Traced it through BullMQ, ioredis, Node, and Railway.
25
+ • Then added explicit shutdown handling across 17 cron processes.
18
26
 
19
- The modern application is unpaid data entry with extra steps. Upload the PDF. Then type the PDF into twelve boxes the parser already filled wrong. Then salary in USD, GBP, and emotional damage. Then a culture quiz written by someone who has never been in the culture. Then "why this company?" as if Greenhouse is going to light a candle and read it.
27
+ h hire me · 1 proof · 5 rant · q quit
28
+ ```
20
29
 
21
- Submit. Rejected in 400ms. Sometimes the no arrives before the confirmation. Five years in production, $100K pulled out of infra, payment rails that moved billions — filtered out because I did not write "synergy" enough times. Cool product. Would not ship it.
30
+ ```bash
31
+ npx jobless hire
32
+ npx jobless work
33
+ npx jobless rant
34
+ ```
22
35
 
23
- ### They booked the call so they had something to cancel
36
+ ## Hire me
24
37
 
25
- Favorite genre of fiction: the recruiter calendar invite. "Loved your profile. Thursday 10am?" I clear the morning. I make tea. I put on a shirt with buttons. 9:58am: "we've decided to move forward with other candidates." Which other candidates. The ones you have not scheduled yet?
38
+ - Email: [yonalem21@gmail.com](mailto:yonalem21@gmail.com?subject=npx%20jobless%20%E2%80%94%20Yonas%20Alem)
39
+ - Site: [yonasalem.vercel.app](https://yonasalem.vercel.app)
40
+ - GitHub: [github.com/Yonas21](https://github.com/Yonas21)
41
+ - LinkedIn: [linkedin.com/in/yonasalem21](https://www.linkedin.com/in/yonasalem21/)
42
+ - Resume: [PDF](https://yonasalem.vercel.app/assets/resume.pdf)
26
43
 
27
- If ghosting is the process, skip the theater. I debug silent failures for a living. Yours is not that silent.
44
+ In the CLI, press **`h`**.
28
45
 
29
- ### LinkedIn: 40% more thought leadership, 0% more jobs
46
+ ## Proof
30
47
 
31
- LinkedIn used to be a jobs board that accidentally had a feed. Now it is a feed that accidentally has jobs. Everyone is humbled. Everyone is announcing. Everyone is a thought leader on a Tuesday. The algorithm loves a carousel called "10 habits of senior engineers" from someone who has never been on-call. It does not love the person who actually kept production up.
48
+ - Cut infrastructure cost ~35% (~$100K / year)
49
+ - Improved platform performance ~30%
50
+ - Payment rails that handled **3B+ ETB**
51
+ - Diagnosed sync jobs that died after every deploy; fixed shutdown across **17 cron processes**
52
+ - Amazon SP-API / Ads, PostgreSQL, Node.js, BullMQ, Redis, React, AWS
32
53
 
33
- I did not open this app to like your new chapter. I opened it because I can build the thing you are posting about.
54
+ Source: [github.com/Yonas21/jobless](https://github.com/Yonas21/jobless)
34
55
 
35
- ```bash
36
- npx jobless rant
37
- ```
56
+ ## The market
38
57
 
39
- ## Yonas Alem
58
+ Dessert. Press `5` or run `npx jobless rant`.
40
59
 
41
- Senior Software Engineer in Addis Ababa. Open to remote senior roles.
60
+ ### I do not know their coffee, their mug, or their stars
42
61
 
43
- 5+ years in production · 35% infra cost cut · ~30% performance gain · 3B+ ETB payment volume
62
+ The job posting said TypeScript. The actual exam is telepathy. Oat milk or "just black, I ship." Navy mug or emotional-support Stanley. Virgo, Gemini, or Scorpio. I can trace a silent cron death across 17 processes. I cannot see into their mug.
44
63
 
45
- - Site: [yonasalem.vercel.app](https://yonasalem.vercel.app)
46
- - GitHub: [github.com/Yonas21](https://github.com/Yonas21)
47
- - LinkedIn: [linkedin.com/in/yonasalem21](https://www.linkedin.com/in/yonasalem21/)
48
- - Email: yonalem21@gmail.com
64
+ ### I applied for 30 minutes. The bot needed 400ms
49
65
 
50
- ```bash
51
- npx jobless hire
52
- ```
66
+ Upload the PDF. Type the PDF. Culture quiz. Submit. Rejected in 400ms. Five years in production, filtered out for missing "synergy." Cool product. Would not ship it.
67
+
68
+ ### They booked the call so they had something to cancel
69
+
70
+ "Loved your profile. Thursday 10am?" Shirt with buttons. 9:58am: other candidates. I debug silent failures for a living. Yours is not that silent.
71
+
72
+ ### LinkedIn: 40% more thought leadership, 0% more jobs
73
+
74
+ A jobs board that accidentally had a feed. Now a feed that accidentally has jobs.
53
75
 
54
76
  ## Commands
55
77
 
56
78
  ```bash
57
- npx jobless # interactive
58
- npx jobless rant # the market, with jokes
59
- npx jobless who # who I am
60
- npx jobless looking # roles I want
61
- npx jobless work # proof of work
62
- npx jobless skills # the stack
63
- npx jobless hire # links + email
79
+ npx jobless # resume first, rant if you ask
80
+ npx jobless work # proof
81
+ npx jobless hire # email + links
82
+ npx jobless who # bio
83
+ npx jobless looking # roles
84
+ npx jobless skills # stack
85
+ npx jobless rant # the market
86
+ npx jobless --joke # ATS rejection intro
64
87
  ```
65
88
 
66
89
  Zero dependencies. Node 18+.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jobless",
3
- "version": "1.1.0",
4
- "description": "npx jobless — I still do not know the recruiter's coffee, mug color, or star sign. A terminal resume for a broken job market.",
3
+ "version": "1.2.0",
4
+ "description": "npx jobless — senior software engineer terminal resume. Production systems, not carousels.",
5
5
  "keywords": [
6
6
  "cli",
7
7
  "npx",
@@ -22,8 +22,13 @@
22
22
  },
23
23
  "homepage": "https://yonasalem.vercel.app",
24
24
  "bugs": {
25
+ "url": "https://github.com/Yonas21/jobless/issues",
25
26
  "email": "yonalem21@gmail.com"
26
27
  },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/Yonas21/jobless.git"
31
+ },
27
32
  "type": "module",
28
33
  "bin": {
29
34
  "jobless": "bin/jobless.js"
package/src/cli.js CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  printLooking,
21
21
  printMenu,
22
22
  printRant,
23
+ printSignature,
23
24
  printSkills,
24
25
  printWho,
25
26
  printWork,
@@ -38,9 +39,11 @@ const COMMANDS = {
38
39
  rant: printRant,
39
40
  market: printRant,
40
41
  work: printWork,
42
+ proof: printWork,
41
43
  experience: printWork,
42
44
  skills: printSkills,
43
45
  hire: printHire,
46
+ h: printHire,
44
47
  links: printHire,
45
48
  contact: printHire,
46
49
  };
@@ -54,6 +57,7 @@ const resolveCommand = (input) => {
54
57
  const value = String(input).trim().toLowerCase();
55
58
  if (!value) return null;
56
59
  if (value === 'q' || value === 'quit' || value === 'exit') return 'quit';
60
+ if (value === 'help') return 'help';
57
61
  const fromMenu = menu.find((item) => item.key === value || item.command === value);
58
62
  if (fromMenu) return fromMenu.command;
59
63
  if (COMMANDS[value]) return value;
@@ -67,10 +71,11 @@ const runCommand = (command) => {
67
71
  return true;
68
72
  };
69
73
 
70
- const interactive = async ({ animate }) => {
74
+ const interactive = async ({ joke }) => {
71
75
  if (process.stdout.isTTY) clear();
72
76
  printHeader();
73
- if (animate) {
77
+ printSignature();
78
+ if (joke) {
74
79
  blank();
75
80
  await printBoot();
76
81
  }
@@ -84,12 +89,19 @@ const interactive = async ({ animate }) => {
84
89
  const command = resolveCommand(answer);
85
90
 
86
91
  if (!command) {
87
- print(` ${dim('unknown. try 1–6 or q.')}`);
92
+ print(` ${dim('unknown. try 1–5, h to hire, or q.')}`);
93
+ continue;
94
+ }
95
+ if (command === 'help') {
96
+ if (process.stdout.isTTY) clear();
97
+ printHelp();
98
+ printMenu();
88
99
  continue;
89
100
  }
90
101
  if (command === 'quit') {
91
102
  blank();
92
103
  print(dim(' still jobless. still shipping.'));
104
+ print(dim(' yonalem21@gmail.com'));
93
105
  blank();
94
106
  break;
95
107
  }
@@ -114,7 +126,7 @@ const parse = (argv) => {
114
126
  export const run = async (argv = []) => {
115
127
  const { flags, rest } = parse(argv);
116
128
 
117
- if (flags.has('-h') || flags.has('--help')) {
129
+ if (flags.has('-h') || flags.has('--help') || rest[0] === 'help') {
118
130
  printHelp();
119
131
  return;
120
132
  }
@@ -129,22 +141,21 @@ export const run = async (argv = []) => {
129
141
  return;
130
142
  }
131
143
 
132
- const animate = process.stdout.isTTY && !flags.has('--no-anim');
144
+ const joke = flags.has('--joke');
133
145
  const command = rest[0] ? resolveCommand(rest[0]) : null;
134
146
 
135
- if (command && command !== 'quit') {
147
+ if (command && command !== 'quit' && command !== 'help') {
136
148
  printHeader();
137
149
  runCommand(command);
138
150
  blank();
139
- print(dim(' more: npx jobless or npx jobless --help'));
151
+ print(dim(' more: npx jobless · npx jobless hire · npx jobless --help'));
140
152
  return;
141
153
  }
142
154
 
143
155
  if (!process.stdout.isTTY) {
144
156
  printHeader();
157
+ printSignature();
145
158
  printWho();
146
- printLooking();
147
- printRant();
148
159
  printHire({ copy: false });
149
160
  return;
150
161
  }
@@ -155,5 +166,5 @@ export const run = async (argv = []) => {
155
166
  process.exit(0);
156
167
  });
157
168
 
158
- await interactive({ animate });
169
+ await interactive({ joke });
159
170
  };
package/src/data.js CHANGED
@@ -9,11 +9,16 @@ export const profile = {
9
9
  phone: '+251 936 972 697',
10
10
  website: 'https://yonasalem.vercel.app',
11
11
  github: 'https://github.com/Yonas21',
12
+ repo: 'https://github.com/Yonas21/jobless',
12
13
  linkedin: 'https://www.linkedin.com/in/yonasalem21/',
13
14
  instagram: 'https://www.instagram.com/its_yon_21/',
14
15
  resume: 'https://yonasalem.vercel.app/assets/resume.pdf',
15
16
  };
16
17
 
18
+ export const hireMailto = `mailto:${profile.email}?subject=${encodeURIComponent(
19
+ 'npx jobless — Yonas Alem',
20
+ )}&body=${encodeURIComponent('I ran npx jobless. I want to talk about a role.\n')}`;
21
+
17
22
  export const stats = [
18
23
  { value: '5+', label: 'years in production' },
19
24
  { value: '35%', label: 'infra cost cut' },
@@ -21,6 +26,16 @@ export const stats = [
21
26
  { value: '3B+', label: 'ETB payment volume' },
22
27
  ];
23
28
 
29
+ export const signature = {
30
+ title: 'The deploy that killed 17 cron jobs',
31
+ lines: [
32
+ 'Recurring Amazon sync jobs silently stopped after every deploy.',
33
+ 'Traced it through BullMQ, ioredis, the Node process lifecycle, and the Railway scheduler.',
34
+ 'Then added explicit shutdown handling across 17 cron processes.',
35
+ 'I debug silent production failures. That is the job.',
36
+ ],
37
+ };
38
+
24
39
  export const about = [
25
40
  'I build and fix production systems across payments, gaming, ecommerce, advertising, and government services — from PostgreSQL and backend services through React, AWS, and the last mile of production debugging.',
26
41
  'I have cut infrastructure spend by about 35% (roughly $100K a year), improved platform performance around 30%, and integrated payment rails that have handled more than 3 billion ETB in transaction volume.',
@@ -169,12 +184,12 @@ export const quotes = [
169
184
  ];
170
185
 
171
186
  export const menu = [
172
- { key: '1', command: 'who', label: 'who I am' },
173
- { key: '2', command: 'looking', label: 'roles I want' },
174
- { key: '3', command: 'rant', label: 'the market, with jokes' },
175
- { key: '4', command: 'work', label: 'proof of work' },
176
- { key: '5', command: 'skills', label: 'the stack' },
177
- { key: '6', command: 'hire', label: 'hire me / links' },
187
+ { key: '1', command: 'work', label: 'proof of work' },
188
+ { key: '2', command: 'who', label: 'who I am' },
189
+ { key: '3', command: 'looking', label: 'roles I want' },
190
+ { key: '4', command: 'skills', label: 'the stack' },
191
+ { key: '5', command: 'rant', label: 'the market, with jokes' },
192
+ { key: 'h', command: 'hire', label: 'hire me' },
178
193
  { key: 'q', command: 'quit', label: 'leave the void' },
179
194
  ];
180
195
 
package/src/screens.js CHANGED
@@ -4,11 +4,13 @@ import {
4
4
  about,
5
5
  bootLines,
6
6
  experience,
7
+ hireMailto,
7
8
  lookingFor,
8
9
  menu,
9
10
  profile,
10
11
  quotes,
11
12
  rants,
13
+ signature,
12
14
  skills,
13
15
  stats,
14
16
  work,
@@ -67,7 +69,26 @@ export const printHeader = () => {
67
69
  print(` ${statLine(stats.slice(2))}`);
68
70
  blank();
69
71
  print(
70
- ` ${link(profile.email, `mailto:${profile.email}`)}${dim(' · ')}${link(profile.website, profile.website)}${dim(' · ')}${link(profile.github.replace('https://', ''), profile.github)}`,
72
+ ` ${link(profile.email, hireMailto)}${dim(' · ')}${link(profile.website, profile.website)}${dim(' · ')}${link(
73
+ profile.github.replace('https://', ''),
74
+ profile.github,
75
+ )}`,
76
+ );
77
+ };
78
+
79
+ export const printSignature = () => {
80
+ blank();
81
+ section('PROOF');
82
+ blank();
83
+ print(` ${bold(signature.title)}`);
84
+ blank();
85
+ for (const line of signature.lines) bullet(line);
86
+ };
87
+
88
+ export const printHints = () => {
89
+ blank();
90
+ print(
91
+ ` ${bold(green('h'))} hire me ${dim('·')} ${bold('1')} proof ${dim('·')} ${bold('5')} rant ${dim('·')} ${bold('q')} quit`,
71
92
  );
72
93
  };
73
94
 
@@ -84,7 +105,7 @@ export const printBoot = async () => {
84
105
  print(` ${bold(red('RESULT'))} ${bold('REJECTED')}`);
85
106
  print(` ${dim('reason')} guessed white mug. it was navy. also a Virgo.`);
86
107
  print(` ${dim('elapsed')} 0.40s (personal best)`);
87
- print(` ${dim('note')} the loading bar is the joke. the rejection is documentary.`);
108
+ print(` ${dim('note')} the loading bar is the joke. the work is not.`);
88
109
  } finally {
89
110
  showCursor();
90
111
  }
@@ -135,11 +156,13 @@ export const printRant = () => {
135
156
  }
136
157
  blank();
137
158
  print(` ${dim('Still here? Congrats. You already outperformed the ATS.')}`);
159
+ print(` ${dim('If you want the work, press')} ${bold('1')}${dim('. If you want to hire, press')} ${bold(green('h'))}${dim('.')}`);
138
160
  };
139
161
 
140
162
  export const printWork = () => {
163
+ printSignature();
141
164
  blank();
142
- section('PROOF');
165
+ print(` ${dim('experience')}`);
143
166
  blank();
144
167
  for (const job of experience) {
145
168
  print(` ${bold(job.role)}`);
@@ -182,7 +205,7 @@ export const printHire = ({ copy = true } = {}) => {
182
205
  blank();
183
206
  print(` ${dim('Skip the 47-field form. This is the whole application.')}`);
184
207
  blank();
185
- kv('Email', link(profile.email, `mailto:${profile.email}`));
208
+ kv('Email', link(profile.email, hireMailto));
186
209
  kv('Phone', link(profile.phone, `tel:${profile.phone.replace(/\s+/g, '')}`));
187
210
  kv('Site', link(profile.website, profile.website));
188
211
  kv('GitHub', link(profile.github, profile.github));
@@ -192,8 +215,9 @@ export const printHire = ({ copy = true } = {}) => {
192
215
  if (copy && process.stdout.isTTY && copyToClipboard(profile.email)) {
193
216
  print(` ${green('copied')} ${profile.email} ${dim('to clipboard')}`);
194
217
  }
218
+ print(` ${dim('or')} ${link('open a prefilled email', hireMailto)}`);
195
219
  blank();
196
- print(` ${dim('If you got this far, you already did more than most ATS pipelines.')}`);
220
+ print(` ${dim('Remote senior / full-stack. I can start a conversation this week.')}`);
197
221
  };
198
222
 
199
223
  export const printMenu = () => {
@@ -204,32 +228,30 @@ export const printMenu = () => {
204
228
  { title: 'open a door' },
205
229
  ),
206
230
  );
231
+ printHints();
207
232
  blank();
208
233
  };
209
234
 
210
235
  export const printHelp = () => {
211
236
  print(banner());
212
237
  blank();
213
- print(` ${bold('npx jobless')} interactive`);
238
+ print(` ${bold('npx jobless')} interactive resume`);
239
+ print(` ${bold('npx jobless work')} proof of work`);
240
+ print(` ${bold('npx jobless hire')} email + links`);
214
241
  print(` ${bold('npx jobless who')} bio`);
215
242
  print(` ${bold('npx jobless looking')} roles`);
216
- print(` ${bold('npx jobless rant')} the market`);
217
- print(` ${bold('npx jobless work')} experience`);
218
243
  print(` ${bold('npx jobless skills')} stack`);
219
- print(` ${bold('npx jobless hire')} links + email`);
244
+ print(` ${bold('npx jobless rant')} the market`);
245
+ print(` ${bold('npx jobless --joke')} ATS rejection intro`);
220
246
  print(` ${bold('npx jobless --json')} machine readable`);
221
- print(` ${bold('npx jobless --no-anim')} skip the ATS joke`);
222
247
  blank();
223
- print(` ${dim('Built because the feed would not shut up.')}`);
248
+ print(` ${dim('In the prompt:')} ${bold(green('h'))} ${dim('hires,')} ${bold('1')} ${dim('is proof,')} ${bold('q')} ${dim('quits.')}`);
224
249
  print(` ${dim(profile.website)}`);
225
250
  };
226
251
 
227
252
  export const printLandingTeaser = () => {
228
253
  blank();
229
- print(` ${yellow('LinkedIn is a content farm with a jobs tab. This is the profile.')}`);
230
- print(` ${dim('Applications take 30 minutes. The no takes 400ms.')}`);
231
- print(` ${dim('Recruiters book the call so they have something to cancel.')}`);
232
- print(` ${dim('I still do not know their coffee, their mug, or their star sign.')}`);
254
+ print(` ${dim('The rant is option')} ${bold('5')}${dim('. The work is already on screen.')}`);
233
255
  };
234
256
 
235
257
  export const toJson = () =>
@@ -246,7 +268,9 @@ export const toJson = () =>
246
268
  github: profile.github,
247
269
  linkedin: profile.linkedin,
248
270
  resume: profile.resume,
271
+ mailto: hireMailto,
249
272
  },
273
+ signature,
250
274
  lookingFor,
251
275
  rants,
252
276
  stats,
package/src/style.js CHANGED
@@ -118,7 +118,7 @@ export const banner = () => {
118
118
  return box(
119
119
  [
120
120
  bold(brightRed(pad('JOBLESS', max, 'center'))),
121
- dim(pad('a terminal resume for a broken job market', max, 'center')),
121
+ dim(pad('senior engineer. production systems. not a carousel.', max, 'center')),
122
122
  ],
123
123
  { title: 'npx jobless' },
124
124
  );