jobless 1.4.0 → 1.6.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.
@@ -0,0 +1,248 @@
1
+ import { copyToClipboard } from './clip.js';
2
+ import { printFit } from './lint-screens.js';
3
+ import {
4
+ about,
5
+ cases,
6
+ claims,
7
+ experience,
8
+ hireMailto,
9
+ lookingFor,
10
+ mailtoFor,
11
+ mentorship,
12
+ profile,
13
+ quotes,
14
+ signature,
15
+ skills,
16
+ stats,
17
+ vcard,
18
+ } from './data.js';
19
+ import {
20
+ banner,
21
+ blank,
22
+ bold,
23
+ bullet,
24
+ cyan,
25
+ dim,
26
+ green,
27
+ kv,
28
+ link,
29
+ magenta,
30
+ print,
31
+ section,
32
+ wrap,
33
+ yellow,
34
+ } from './style.js';
35
+
36
+ export const printHeader = () => {
37
+ print(banner());
38
+ blank();
39
+ print(` ${bold(cyan(profile.name))}`);
40
+ print(` ${profile.title}`);
41
+ print(` ${dim(`${profile.location} · ${profile.availability}`)}`);
42
+ blank();
43
+ const statLine = (items) =>
44
+ items.map((item) => `${bold(green(item.value))} ${dim(item.label)}`).join(dim(' '));
45
+ print(` ${statLine(stats.slice(0, 2))}`);
46
+ print(` ${statLine(stats.slice(2))}`);
47
+ blank();
48
+ print(
49
+ ` ${link(profile.email, hireMailto)}${dim(' · ')}${link(profile.website, profile.website)}${dim(' · ')}${link(
50
+ profile.github.replace('https://', ''),
51
+ profile.github,
52
+ )}`,
53
+ );
54
+ };
55
+
56
+ export const printSignature = () => {
57
+ blank();
58
+ section('PROOF');
59
+ blank();
60
+ print(` ${bold(signature.title)}`);
61
+ blank();
62
+ for (const line of signature.lines) bullet(line);
63
+ };
64
+
65
+ export const printWho = () => {
66
+ blank();
67
+ section('WHO');
68
+ blank();
69
+ for (const paragraph of about) {
70
+ bullet(paragraph);
71
+ blank();
72
+ }
73
+ kv('Now', profile.currently);
74
+ kv('Study', profile.study);
75
+ kv('Site', link(profile.website, profile.website));
76
+ blank();
77
+ print(` ${dim('numbers, with owners')}`);
78
+ blank();
79
+ for (const claim of claims) {
80
+ print(` ${bold(claim.value)} ${dim(claim.label)}`);
81
+ print(` ${dim(claim.who)}`);
82
+ for (const line of wrap(claim.method)) print(` ${line}`);
83
+ blank();
84
+ }
85
+ };
86
+
87
+ export const printLooking = () => {
88
+ blank();
89
+ section('WHAT I WANT');
90
+ blank();
91
+ print(` ${dim('roles')}`);
92
+ for (const role of lookingFor.roles) bullet(role, green);
93
+ blank();
94
+ print(` ${dim('setup')}`);
95
+ for (const item of lookingFor.setup) bullet(item);
96
+ blank();
97
+ print(` ${dim('yes')}`);
98
+ for (const item of lookingFor.wants) bullet(item);
99
+ blank();
100
+ print(` ${dim('hard no')}`);
101
+ for (const item of lookingFor.no) bullet(item, yellow);
102
+ };
103
+
104
+ export const printCard = () => {
105
+ blank();
106
+ print(` ${bold(signature.title)}`);
107
+ print(` ${dim(signature.lines.at(-1))}`);
108
+ blank();
109
+ kv('Email', link(profile.email, hireMailto));
110
+ kv('Site', link(profile.website, profile.website));
111
+ kv('GitHub', link(profile.github, profile.github));
112
+ blank();
113
+ print(` ${dim('Remote senior / full-stack. Press')} ${bold(green('h'))} ${dim('or email from here.')}`);
114
+ };
115
+
116
+ const printCase = (item, { deep = false } = {}) => {
117
+ print(` ${bold(item.title)}`);
118
+ print(` ${cyan(item.company)} ${dim(`${item.role} · ${item.kind}`)}`);
119
+ blank();
120
+ print(` ${dim('problem')}`);
121
+ for (const line of wrap(item.problem)) print(` ${line}`);
122
+ if (deep && item.trace) {
123
+ blank();
124
+ print(` ${dim('trace')}`);
125
+ for (const step of item.trace) bullet(step);
126
+ }
127
+ blank();
128
+ print(` ${dim('change')}`);
129
+ for (const line of wrap(item.change)) print(` ${line}`);
130
+ blank();
131
+ print(` ${dim('result')}`);
132
+ for (const line of wrap(item.result)) print(` ${line}`);
133
+ if (deep && item.still) {
134
+ blank();
135
+ print(` ${dim('still open')}`);
136
+ for (const line of wrap(item.still)) print(` ${line}`);
137
+ }
138
+ if (deep && item.undo) {
139
+ blank();
140
+ print(` ${dim('what I would undo')}`);
141
+ for (const line of wrap(item.undo)) print(` ${line}`);
142
+ }
143
+ if (deep && item.ask) {
144
+ blank();
145
+ print(` ${dim('ask me on the call')}`);
146
+ for (const question of item.ask) bullet(question);
147
+ }
148
+ if (item.url) {
149
+ blank();
150
+ print(` ${link(item.url.replace('https://', ''), item.url)}`);
151
+ }
152
+ print(` ${dim(item.stack.join(' · '))}`);
153
+ };
154
+
155
+ export const printProof = () => {
156
+ blank();
157
+ section('PROOF');
158
+ blank();
159
+ printCase(cases[0], { deep: true });
160
+ blank();
161
+ print(` ${dim('The other two case studies are')} ${bold('1')}${dim('. Hire is')} ${bold(green('h'))}${dim('.')}`);
162
+ };
163
+
164
+ export const printVcf = ({ copy = false } = {}) => {
165
+ const card = vcard();
166
+ blank();
167
+ section('VCARD');
168
+ blank();
169
+ for (const line of card.trim().split('\n')) print(` ${line}`);
170
+ blank();
171
+ if (copy && process.stdout.isTTY && copyToClipboard(card)) {
172
+ print(` ${green('copied')} vCard`);
173
+ }
174
+ };
175
+
176
+ export const printWork = () => {
177
+ blank();
178
+ section('CASE STUDIES');
179
+ blank();
180
+ print(` ${dim('Private production systems. I will walk through them on a call.')}`);
181
+ for (const item of cases) {
182
+ blank();
183
+ printCase(item, { deep: true });
184
+ }
185
+ blank();
186
+ print(` ${dim('mentorship')}`);
187
+ blank();
188
+ print(` ${bold(mentorship.who)} ${dim(mentorship.where)}`);
189
+ print(` ${mentorship.on.join(dim(' · '))}`);
190
+ for (const line of wrap(mentorship.still)) print(` ${line}`);
191
+ blank();
192
+ print(` ${dim('experience')}`);
193
+ blank();
194
+ for (const job of experience) {
195
+ print(` ${bold(job.role)}`);
196
+ print(` ${cyan(job.company)} ${dim(`${job.dates} · ${job.place}`)}`);
197
+ blank();
198
+ for (const point of job.points) bullet(point);
199
+ print(` ${dim(job.stack.join(' · '))}`);
200
+ blank();
201
+ }
202
+ print(` ${dim('people who were not bots')}`);
203
+ blank();
204
+ for (const quote of quotes) {
205
+ print(` ${magenta(`"${quote.text}"`)}`);
206
+ print(` ${dim(`— ${quote.name}${quote.role ? `, ${quote.role}` : ''}`)}`);
207
+ blank();
208
+ }
209
+ };
210
+
211
+ export const printSkills = () => {
212
+ blank();
213
+ section('STACK');
214
+ blank();
215
+ for (const group of skills) {
216
+ print(` ${bold(group.name)} ${dim(group.note)}`);
217
+ print(` ${group.items.join(dim(' · '))}`);
218
+ blank();
219
+ }
220
+ };
221
+
222
+ export const printHire = ({ copy = false, report, to } = {}) => {
223
+ const mailto = mailtoFor({ report, to });
224
+ blank();
225
+ section('HIRE ME');
226
+ blank();
227
+ print(` ${dim('Skip the 47-field form. This is the whole application.')}`);
228
+ blank();
229
+ kv('Email', link(profile.email, mailto));
230
+ kv('Phone', link(profile.phone, `tel:${profile.phone.replace(/\s+/g, '')}`));
231
+ kv('Site', link(profile.website, profile.website));
232
+ kv('GitHub', link(profile.github, profile.github));
233
+ kv('LinkedIn', link(profile.linkedin, profile.linkedin));
234
+ kv('Resume', link(profile.resume, profile.resume));
235
+ blank();
236
+ if (report?.letter) {
237
+ print(` ${dim('last lint')} ${bold(report.letter.result)} ${dim(`${report.score}/100`)}`);
238
+ blank();
239
+ }
240
+ if (report?.fit) printFit(report.fit, { failed: !report.ok });
241
+ if (copy && process.stdout.isTTY && copyToClipboard(profile.email)) {
242
+ print(` ${green('copied')} ${profile.email} ${dim('to clipboard')}`);
243
+ }
244
+ print(` ${dim('or')} ${link('open a prefilled email', mailto)}`);
245
+ blank();
246
+ print(` ${dim('Remote senior / full-stack. I can start a conversation this week.')}`);
247
+ };
248
+
package/src/reject.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  bold,
5
5
  box,
6
6
  dim,
7
+ green,
7
8
  hideCursor,
8
9
  inner,
9
10
  print,
@@ -14,6 +15,15 @@ import {
14
15
  yellow,
15
16
  } from './style.js';
16
17
 
18
+ export const ATS = [
19
+ { name: 'Greenhouse', from: 'Greenhouse <no-reply@not-a-human.com>', center: 'Greenhouse Applicant Center' },
20
+ { name: 'Lever', from: 'Lever <no-reply@not-a-human.com>', center: 'Lever Hire' },
21
+ { name: 'Ashby', from: 'Ashby <no-reply@not-a-human.com>', center: 'Ashby Recruiting' },
22
+ { name: 'Workday', from: 'Workday <no-reply@not-a-human.com>', center: 'Workday Recruiting' },
23
+ ];
24
+
25
+ export const pickAts = (seed) => ATS[Math.abs(Number(seed) || 0) % ATS.length];
26
+
17
27
  const BOOT = [
18
28
  'receiving application from the void',
19
29
  'parsing whatever npx just sent',
@@ -28,7 +38,7 @@ const reasonPool = (identity) => {
28
38
  'installed a resume instead of submitting one',
29
39
  `did not mention ${identity.company} in the first 12 words`,
30
40
  'cover letter parsed as "npx"',
31
- 'years of experience with Greenhouse: 0',
41
+ 'years of experience with this ATS: 0',
32
42
  'failed culture add (ran a CLI)',
33
43
  'resume was a person',
34
44
  ];
@@ -41,27 +51,89 @@ const reasonPool = (identity) => {
41
51
  return pool;
42
52
  };
43
53
 
44
- const pickReasons = (identity, count = 2) => {
54
+ export const trapStats = (seed) => ({
55
+ score: 11 + (Math.abs(Number(seed) || 0) % 28),
56
+ pile: 180 + (Math.abs(Number(seed) || 0) % 820),
57
+ });
58
+
59
+ export const pickReasons = (identity, count = 2, seed = seedFrom(identity)) => {
45
60
  const pool = reasonPool(identity);
46
- const seed = seedFrom(identity);
47
61
  const picked = [];
62
+ const numeric = Number(seed) || 0;
48
63
  for (let i = 0; i < count && pool.length; i += 1) {
49
- const index = Math.abs(seed + i * 17) % pool.length;
64
+ const index = Math.abs(numeric + i * 17) % pool.length;
50
65
  picked.push(pool.splice(index, 1)[0]);
51
66
  }
52
67
  return picked;
53
68
  };
54
69
 
55
- export const printTrap = async ({ animate = true } = {}) => {
56
- const identity = detectIdentity();
57
- const reasons = pickReasons(identity);
58
- const to = identity.email || identity.username;
59
- const company = identity.company === 'your team' ? 'this role' : identity.company;
70
+ const letterBody = (letter) =>
71
+ letter.result === 'REJECTED'
72
+ ? 'Thank you for your interest in my labor. After careful consideration of your job description, I will not be moving forward.'
73
+ : 'Thank you for your interest in my labor. Your posting has been held for review. Fix the warnings and resubmit.';
74
+
75
+ export const letterText = (letter) => {
76
+ if (!letter) return '';
77
+ return [
78
+ `FROM ${letter.from}`,
79
+ `TO ${letter.to}`,
80
+ `SUBJECT ${letter.subject}`,
81
+ '',
82
+ letterBody(letter),
83
+ '',
84
+ `RESULT ${letter.result}`,
85
+ `reason ${letter.reasons[0] || 'failed the human compiler'}`,
86
+ ...(letter.reasons[1] ? [`also ${letter.reasons[1]}`] : []),
87
+ 'elapsed 0.40s',
88
+ 'next we will keep your posting on file. we will not.',
89
+ '',
90
+ 'that was not a recruiter.',
91
+ 'that was you, finally answering.',
92
+ ].join('\n');
93
+ };
94
+
95
+ export const trapModel = ({ identity, seed } = {}) => {
96
+ const person = identity || detectIdentity();
97
+ const numericSeed = seed == null || seed === '' ? seedFrom(person) : Number(seed);
98
+ const ats = pickAts(numericSeed);
99
+ const reasons = pickReasons(person, 2, numericSeed);
100
+ const stats = trapStats(numericSeed);
101
+ const to = person.email || person.username;
102
+ const company = person.company === 'your team' ? 'this role' : person.company;
103
+ return { identity: person, seed: numericSeed, ats, reasons, stats, to, company };
104
+ };
105
+
106
+ export const trapText = (model) => {
107
+ const { ats, identity, to, company, reasons, stats } = model;
108
+ return [
109
+ `FROM ${ats.from}`,
110
+ `TO ${identity.name} <${to}>`,
111
+ 'SUBJECT Your application for Senior Engineer',
112
+ '',
113
+ `Thank you for your interest in ${company}. After careful consideration of your application, we will not be moving forward.`,
114
+ '',
115
+ 'RESULT REJECTED',
116
+ `reason ${reasons[0]}`,
117
+ ...(reasons[1] ? [`also ${reasons[1]}`] : []),
118
+ `score ${stats.score}/100`,
119
+ `in pile ${stats.pile} applications`,
120
+ 'elapsed 0.40s',
121
+ 'next we will keep your resume on file. we will not.',
122
+ '',
123
+ 'that was not a company.',
124
+ 'that was the market, in your own terminal.',
125
+ ].join('\n');
126
+ };
127
+
128
+ export const printTrap = async ({ animate = true, seed, identity } = {}) => {
129
+ const model = trapModel({ identity, seed });
130
+ const { ats, reasons, stats, to, company } = model;
131
+ const person = model.identity;
60
132
 
61
133
  if (animate) {
62
134
  hideCursor();
63
135
  try {
64
- print(dim(' Greenhouse Applicant Center'));
136
+ print(dim(` ${ats.center}`));
65
137
  blank();
66
138
  for (const [index, line] of BOOT.entries()) {
67
139
  print(` ${dim(`[${index + 1}/${BOOT.length}]`)} ${line}`);
@@ -77,8 +149,8 @@ export const printTrap = async ({ animate = true } = {}) => {
77
149
 
78
150
  print(
79
151
  box([
80
- dim('FROM Greenhouse <no-reply@not-a-human.com>'),
81
- dim(`TO ${identity.name} <${to}>`),
152
+ dim(`FROM ${ats.from}`),
153
+ dim(`TO ${person.name} <${to}>`),
82
154
  dim(`SUBJECT Your application for Senior Engineer`),
83
155
  '',
84
156
  ...wrap(
@@ -89,6 +161,8 @@ export const printTrap = async ({ animate = true } = {}) => {
89
161
  `${bold(red('RESULT'))} ${bold('REJECTED')}`,
90
162
  `${dim('reason')} ${reasons[0]}`,
91
163
  ...(reasons[1] ? [`${dim('also')} ${reasons[1]}`] : []),
164
+ `${dim('score')} ${stats.score}/100`,
165
+ `${dim('in pile')} ${stats.pile} applications`,
92
166
  `${dim('elapsed')} 0.40s`,
93
167
  `${dim('next')} we will keep your resume on file. we will not.`,
94
168
  ]),
@@ -99,6 +173,8 @@ export const printTrap = async ({ animate = true } = {}) => {
99
173
  print(` ${dim('that was the market, in your own terminal.')}`);
100
174
  blank();
101
175
  print(` ${dim('anyway. here is an engineer who debugs silent production failures.')}`);
176
+ print(` ${dim('the posting that just rejected you:')} ${bold('l')}`);
177
+ print(` ${dim('or skip the theater and hire:')} ${bold(green('h'))}`);
102
178
  };
103
179
 
104
180
  export const printCompanyReject = (letter) => {
@@ -111,12 +187,7 @@ export const printCompanyReject = (letter) => {
111
187
  dim(`TO ${letter.to}`),
112
188
  dim(`SUBJECT ${letter.subject}`),
113
189
  '',
114
- ...wrap(
115
- letter.result === 'REJECTED'
116
- ? 'Thank you for your interest in my labor. After careful consideration of your job description, I will not be moving forward.'
117
- : 'Thank you for your interest in my labor. Your posting has been held for review. Fix the warnings and resubmit.',
118
- inner(),
119
- ),
190
+ ...wrap(letterBody(letter), inner()),
120
191
  '',
121
192
  `${bold(red('RESULT'))} ${bold(letter.result === 'REJECTED' ? red(letter.result) : yellow(letter.result))}`,
122
193
  `${dim('reason')} ${letter.reasons[0] || 'failed the human compiler'}`,