jobless 1.3.0 → 1.5.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 +46 -22
- package/package.json +3 -2
- package/src/cli.js +266 -52
- package/src/clip.js +18 -0
- package/src/data.js +200 -53
- package/src/diff.js +103 -0
- package/src/fit.js +41 -0
- package/src/lint.js +481 -12
- package/src/reject.js +114 -12
- package/src/screens.js +250 -50
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,
|
|
@@ -11,8 +12,18 @@ import {
|
|
|
11
12
|
showCursor,
|
|
12
13
|
sleep,
|
|
13
14
|
wrap,
|
|
15
|
+
yellow,
|
|
14
16
|
} from './style.js';
|
|
15
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
|
+
|
|
16
27
|
const BOOT = [
|
|
17
28
|
'receiving application from the void',
|
|
18
29
|
'parsing whatever npx just sent',
|
|
@@ -27,7 +38,7 @@ const reasonPool = (identity) => {
|
|
|
27
38
|
'installed a resume instead of submitting one',
|
|
28
39
|
`did not mention ${identity.company} in the first 12 words`,
|
|
29
40
|
'cover letter parsed as "npx"',
|
|
30
|
-
'years of experience with
|
|
41
|
+
'years of experience with this ATS: 0',
|
|
31
42
|
'failed culture add (ran a CLI)',
|
|
32
43
|
'resume was a person',
|
|
33
44
|
];
|
|
@@ -40,27 +51,89 @@ const reasonPool = (identity) => {
|
|
|
40
51
|
return pool;
|
|
41
52
|
};
|
|
42
53
|
|
|
43
|
-
const
|
|
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)) => {
|
|
44
60
|
const pool = reasonPool(identity);
|
|
45
|
-
const seed = seedFrom(identity);
|
|
46
61
|
const picked = [];
|
|
62
|
+
const numeric = Number(seed) || 0;
|
|
47
63
|
for (let i = 0; i < count && pool.length; i += 1) {
|
|
48
|
-
const index = Math.abs(
|
|
64
|
+
const index = Math.abs(numeric + i * 17) % pool.length;
|
|
49
65
|
picked.push(pool.splice(index, 1)[0]);
|
|
50
66
|
}
|
|
51
67
|
return picked;
|
|
52
68
|
};
|
|
53
69
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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;
|
|
59
132
|
|
|
60
133
|
if (animate) {
|
|
61
134
|
hideCursor();
|
|
62
135
|
try {
|
|
63
|
-
print(dim(
|
|
136
|
+
print(dim(` ${ats.center}`));
|
|
64
137
|
blank();
|
|
65
138
|
for (const [index, line] of BOOT.entries()) {
|
|
66
139
|
print(` ${dim(`[${index + 1}/${BOOT.length}]`)} ${line}`);
|
|
@@ -76,8 +149,8 @@ export const printTrap = async ({ animate = true } = {}) => {
|
|
|
76
149
|
|
|
77
150
|
print(
|
|
78
151
|
box([
|
|
79
|
-
dim(
|
|
80
|
-
dim(`TO ${
|
|
152
|
+
dim(`FROM ${ats.from}`),
|
|
153
|
+
dim(`TO ${person.name} <${to}>`),
|
|
81
154
|
dim(`SUBJECT Your application for Senior Engineer`),
|
|
82
155
|
'',
|
|
83
156
|
...wrap(
|
|
@@ -88,6 +161,8 @@ export const printTrap = async ({ animate = true } = {}) => {
|
|
|
88
161
|
`${bold(red('RESULT'))} ${bold('REJECTED')}`,
|
|
89
162
|
`${dim('reason')} ${reasons[0]}`,
|
|
90
163
|
...(reasons[1] ? [`${dim('also')} ${reasons[1]}`] : []),
|
|
164
|
+
`${dim('score')} ${stats.score}/100`,
|
|
165
|
+
`${dim('in pile')} ${stats.pile} applications`,
|
|
91
166
|
`${dim('elapsed')} 0.40s`,
|
|
92
167
|
`${dim('next')} we will keep your resume on file. we will not.`,
|
|
93
168
|
]),
|
|
@@ -98,4 +173,31 @@ export const printTrap = async ({ animate = true } = {}) => {
|
|
|
98
173
|
print(` ${dim('that was the market, in your own terminal.')}`);
|
|
99
174
|
blank();
|
|
100
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'))}`);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const printCompanyReject = (letter) => {
|
|
181
|
+
if (!letter) return;
|
|
182
|
+
|
|
183
|
+
blank();
|
|
184
|
+
print(
|
|
185
|
+
box([
|
|
186
|
+
dim(`FROM ${letter.from}`),
|
|
187
|
+
dim(`TO ${letter.to}`),
|
|
188
|
+
dim(`SUBJECT ${letter.subject}`),
|
|
189
|
+
'',
|
|
190
|
+
...wrap(letterBody(letter), inner()),
|
|
191
|
+
'',
|
|
192
|
+
`${bold(red('RESULT'))} ${bold(letter.result === 'REJECTED' ? red(letter.result) : yellow(letter.result))}`,
|
|
193
|
+
`${dim('reason')} ${letter.reasons[0] || 'failed the human compiler'}`,
|
|
194
|
+
...(letter.reasons[1] ? [`${dim('also')} ${letter.reasons[1]}`] : []),
|
|
195
|
+
`${dim('elapsed')} 0.40s`,
|
|
196
|
+
`${dim('next')} we will keep your posting on file. we will not.`,
|
|
197
|
+
]),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
blank();
|
|
201
|
+
print(` ${dim('that was not a recruiter.')}`);
|
|
202
|
+
print(` ${dim('that was you, finally answering.')}`);
|
|
101
203
|
};
|
package/src/screens.js
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { copyToClipboard } from './clip.js';
|
|
2
|
+
import { unifiedDiff } from './diff.js';
|
|
3
|
+
import { tweetFromReport } from './fit.js';
|
|
4
|
+
import { rules } from './lint.js';
|
|
5
|
+
import { letterText, printCompanyReject } from './reject.js';
|
|
2
6
|
|
|
3
7
|
import {
|
|
4
8
|
about,
|
|
9
|
+
cases,
|
|
10
|
+
claims,
|
|
5
11
|
experience,
|
|
6
12
|
hireMailto,
|
|
7
13
|
lookingFor,
|
|
14
|
+
mailtoFor,
|
|
8
15
|
menu,
|
|
16
|
+
mentorship,
|
|
9
17
|
profile,
|
|
10
18
|
quotes,
|
|
11
19
|
rants,
|
|
12
20
|
signature,
|
|
13
21
|
skills,
|
|
14
22
|
stats,
|
|
15
|
-
|
|
23
|
+
vcard,
|
|
16
24
|
} from './data.js';
|
|
17
25
|
import {
|
|
18
26
|
banner,
|
|
@@ -35,23 +43,6 @@ import {
|
|
|
35
43
|
yellow,
|
|
36
44
|
} from './style.js';
|
|
37
45
|
|
|
38
|
-
const copyToClipboard = (text) => {
|
|
39
|
-
try {
|
|
40
|
-
if (process.platform === 'darwin') {
|
|
41
|
-
execFileSync('pbcopy', { input: text });
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
if (process.platform === 'win32') {
|
|
45
|
-
execFileSync('clip', { input: text, windowsHide: true });
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
execFileSync('xclip', ['-selection', 'clipboard'], { input: text });
|
|
49
|
-
return true;
|
|
50
|
-
} catch {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
46
|
export const printHeader = () => {
|
|
56
47
|
print(banner());
|
|
57
48
|
blank();
|
|
@@ -84,7 +75,7 @@ export const printSignature = () => {
|
|
|
84
75
|
export const printHints = () => {
|
|
85
76
|
blank();
|
|
86
77
|
print(
|
|
87
|
-
` ${bold(green('h'))} hire ${dim('·')} ${bold('1')} proof ${dim('·')} ${bold('l')} lint a posting ${dim('·')} ${bold('q')} quit`,
|
|
78
|
+
` ${bold(green('h'))} hire ${dim('·')} ${bold('1')} proof ${dim('·')} ${bold('l')} lint a posting ${dim('·')} ${bold('c')} copy letter ${dim('·')} ${bold('q')} quit`,
|
|
88
79
|
);
|
|
89
80
|
};
|
|
90
81
|
|
|
@@ -99,6 +90,15 @@ export const printWho = () => {
|
|
|
99
90
|
kv('Now', profile.currently);
|
|
100
91
|
kv('Study', profile.study);
|
|
101
92
|
kv('Site', link(profile.website, profile.website));
|
|
93
|
+
blank();
|
|
94
|
+
print(` ${dim('numbers, with owners')}`);
|
|
95
|
+
blank();
|
|
96
|
+
for (const claim of claims) {
|
|
97
|
+
print(` ${bold(claim.value)} ${dim(claim.label)}`);
|
|
98
|
+
print(` ${dim(claim.who)}`);
|
|
99
|
+
for (const line of wrap(claim.method)) print(` ${line}`);
|
|
100
|
+
blank();
|
|
101
|
+
}
|
|
102
102
|
};
|
|
103
103
|
|
|
104
104
|
export const printLooking = () => {
|
|
@@ -136,40 +136,217 @@ export const printRant = () => {
|
|
|
136
136
|
print(` ${dim('If you want the work, press')} ${bold('1')}${dim('. If you want to hire, press')} ${bold(green('h'))}${dim('.')}`);
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
-
export const printLintReport = (
|
|
140
|
-
const { findings } =
|
|
141
|
-
const
|
|
142
|
-
const warnings = findings.filter((item) => item.level === 'warn');
|
|
139
|
+
export const printLintReport = (report, { quiet = false, copied = false } = {}) => {
|
|
140
|
+
const { findings, source, errors, warnings, letter, ok, score } = report;
|
|
141
|
+
const visible = quiet ? findings.filter((item) => item.level === 'error') : findings;
|
|
143
142
|
|
|
144
143
|
blank();
|
|
145
144
|
section('JOBLESS LINT');
|
|
146
145
|
blank();
|
|
147
|
-
print(` ${dim(source)}`);
|
|
146
|
+
print(` ${dim(source)} ${dim('·')} ${bold(`${score}/100`)}`);
|
|
148
147
|
blank();
|
|
149
148
|
|
|
150
|
-
if (!findings.length) {
|
|
149
|
+
if (ok && !findings.length) {
|
|
151
150
|
print(` ${green('clean')} this posting might be a real job.`);
|
|
151
|
+
if (report.fit) printFit(report.fit, { failed: false });
|
|
152
152
|
blank();
|
|
153
153
|
print(` ${dim('If you still want the engineer who wrote the linter:')} ${bold(green('h'))}`);
|
|
154
|
-
return
|
|
154
|
+
return report;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
for (const finding of
|
|
157
|
+
for (const finding of visible) {
|
|
158
158
|
const tag = finding.level === 'error' ? red('error') : yellow('warn ');
|
|
159
159
|
print(` ${tag} ${finding.message}`);
|
|
160
160
|
print(` ${dim(finding.id)}`);
|
|
161
|
+
if (finding.line && finding.snippet) {
|
|
162
|
+
print(` ${dim(`L${finding.line}`)} ${finding.snippet}`);
|
|
163
|
+
}
|
|
161
164
|
blank();
|
|
162
165
|
}
|
|
163
166
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
print(
|
|
168
|
-
|
|
167
|
+
printCompanyReject(letter);
|
|
168
|
+
if (report.fit) printFit(report.fit, { failed: true });
|
|
169
|
+
blank();
|
|
170
|
+
print(
|
|
171
|
+
` ${bold(`${score}/100`)} ${errors} error${errors === 1 ? '' : 's'} ${warnings} warning${warnings === 1 ? '' : 's'} ${dim('·')} ${dim('hire:')} ${bold(green('h'))}${dim(' copy letter:')} ${bold('c')}`,
|
|
172
|
+
);
|
|
173
|
+
if (copied) {
|
|
174
|
+
print(` ${green('copied')} rejection letter`);
|
|
175
|
+
}
|
|
176
|
+
return report;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const printFit = (fit, { failed = false } = {}) => {
|
|
180
|
+
if (!fit) return;
|
|
181
|
+
blank();
|
|
182
|
+
section('FIT');
|
|
183
|
+
blank();
|
|
184
|
+
if (fit.matched.length) print(` ${green('overlap')} ${fit.matched.join(dim(' · '))}`);
|
|
185
|
+
if (fit.missed.length) print(` ${yellow('miss')} ${fit.missed.join(dim(' · '))}`);
|
|
186
|
+
if (fit.extra.length) print(` ${dim('also')} ${fit.extra.join(' · ')}`);
|
|
187
|
+
blank();
|
|
188
|
+
if (fit.score == null) {
|
|
189
|
+
print(` ${dim('The posting did not name a stack. The engineer still exists.')} ${bold(green('h'))}`);
|
|
190
|
+
} else if (failed) {
|
|
191
|
+
print(
|
|
192
|
+
` ${bold(`${fit.score}/100`)} overlap ${dim('·')} ${dim('the posting failed. the engineer still matches:')} ${bold(green('h'))}`,
|
|
193
|
+
);
|
|
194
|
+
} else {
|
|
195
|
+
print(
|
|
196
|
+
` ${bold(`${fit.score}/100`)} overlap ${dim('·')} ${dim('hire the engineer who matches:')} ${bold(green('h'))}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export const printFix = (fixed) => {
|
|
202
|
+
blank();
|
|
203
|
+
section('DIFF');
|
|
204
|
+
blank();
|
|
205
|
+
if (!fixed.applied.length) {
|
|
206
|
+
print(` ${dim('nothing to rewrite.')}`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
print(` ${dim('applied')} ${fixed.applied.join(' · ')}`);
|
|
210
|
+
blank();
|
|
211
|
+
const diff = fixed.diff || unifiedDiff(fixed.before || '', fixed.text, fixed.path || 'posting.txt');
|
|
212
|
+
if (!diff) {
|
|
213
|
+
print(` ${dim('rewrite produced the same lines.')}`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
for (const line of diff.split('\n')) {
|
|
217
|
+
if (line.startsWith('+') && !line.startsWith('+++')) print(` ${green(line)}`);
|
|
218
|
+
else if (line.startsWith('-') && !line.startsWith('---')) print(` ${red(line)}`);
|
|
219
|
+
else print(` ${dim(line)}`);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export const printRules = () => {
|
|
224
|
+
blank();
|
|
225
|
+
section('RULES');
|
|
226
|
+
blank();
|
|
227
|
+
for (const rule of rules) {
|
|
228
|
+
const tag = rule.level === 'error' ? red('error') : yellow('warn ');
|
|
229
|
+
print(` ${tag} ${rule.id}`);
|
|
230
|
+
print(` ${dim(rule.message)}`);
|
|
231
|
+
if (rule.why) print(` ${dim(rule.why)}`);
|
|
232
|
+
blank();
|
|
233
|
+
}
|
|
234
|
+
print(` ${dim('npx jobless lint --off=rockstar-ninja posting.txt')}`);
|
|
235
|
+
print(` ${dim('jobless-disable-next-line no-fake-salary')}`);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export const printCard = () => {
|
|
239
|
+
blank();
|
|
240
|
+
print(` ${bold(signature.title)}`);
|
|
241
|
+
print(` ${dim(signature.lines.at(-1))}`);
|
|
242
|
+
blank();
|
|
243
|
+
kv('Email', link(profile.email, hireMailto));
|
|
244
|
+
kv('Site', link(profile.website, profile.website));
|
|
245
|
+
kv('GitHub', link(profile.github, profile.github));
|
|
246
|
+
blank();
|
|
247
|
+
print(` ${dim('Remote senior / full-stack. Press')} ${bold(green('h'))} ${dim('or email from here.')}`);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const printCase = (item, { deep = false } = {}) => {
|
|
251
|
+
print(` ${bold(item.title)}`);
|
|
252
|
+
print(` ${cyan(item.company)} ${dim(`${item.role} · ${item.kind}`)}`);
|
|
253
|
+
blank();
|
|
254
|
+
print(` ${dim('problem')}`);
|
|
255
|
+
for (const line of wrap(item.problem)) print(` ${line}`);
|
|
256
|
+
if (deep && item.trace) {
|
|
257
|
+
blank();
|
|
258
|
+
print(` ${dim('trace')}`);
|
|
259
|
+
for (const step of item.trace) bullet(step);
|
|
260
|
+
}
|
|
261
|
+
blank();
|
|
262
|
+
print(` ${dim('change')}`);
|
|
263
|
+
for (const line of wrap(item.change)) print(` ${line}`);
|
|
264
|
+
blank();
|
|
265
|
+
print(` ${dim('result')}`);
|
|
266
|
+
for (const line of wrap(item.result)) print(` ${line}`);
|
|
267
|
+
if (deep && item.still) {
|
|
268
|
+
blank();
|
|
269
|
+
print(` ${dim('still open')}`);
|
|
270
|
+
for (const line of wrap(item.still)) print(` ${line}`);
|
|
271
|
+
}
|
|
272
|
+
if (deep && item.undo) {
|
|
273
|
+
blank();
|
|
274
|
+
print(` ${dim('what I would undo')}`);
|
|
275
|
+
for (const line of wrap(item.undo)) print(` ${line}`);
|
|
276
|
+
}
|
|
277
|
+
if (deep && item.ask) {
|
|
278
|
+
blank();
|
|
279
|
+
print(` ${dim('ask me on the call')}`);
|
|
280
|
+
for (const question of item.ask) bullet(question);
|
|
281
|
+
}
|
|
282
|
+
if (item.url) {
|
|
283
|
+
blank();
|
|
284
|
+
print(` ${link(item.url.replace('https://', ''), item.url)}`);
|
|
285
|
+
}
|
|
286
|
+
print(` ${dim(item.stack.join(' · '))}`);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export const printProof = () => {
|
|
290
|
+
blank();
|
|
291
|
+
section('PROOF');
|
|
292
|
+
blank();
|
|
293
|
+
printCase(cases[0], { deep: true });
|
|
294
|
+
blank();
|
|
295
|
+
print(` ${dim('The other two case studies are')} ${bold('1')}${dim('. Hire is')} ${bold(green('h'))}${dim('.')}`);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export const printTweet = (report) => {
|
|
299
|
+
blank();
|
|
300
|
+
print(tweetFromReport(report));
|
|
301
|
+
blank();
|
|
302
|
+
print(` ${dim('Post the command, not the punchline.')}`);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export const printWhy = () => {
|
|
306
|
+
blank();
|
|
307
|
+
section('WHY');
|
|
308
|
+
blank();
|
|
309
|
+
for (const line of [
|
|
310
|
+
'The market parses humans in 400ms and calls it hiring.',
|
|
311
|
+
'jobless parses you first, then parses the job post.',
|
|
312
|
+
'It is a reverse ATS: the company has to pass.',
|
|
313
|
+
'Then it shows the engineer who debugs silent production failures.',
|
|
314
|
+
'Local only. No apply. No email sent. No scrape.',
|
|
315
|
+
'If the joke lands, press h.',
|
|
316
|
+
]) {
|
|
317
|
+
bullet(line);
|
|
318
|
+
}
|
|
319
|
+
blank();
|
|
320
|
+
print(` ${dim('Hire is')} ${bold(green('h'))}${dim('.')}`);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export const printVcf = ({ copy = true } = {}) => {
|
|
324
|
+
const card = vcard();
|
|
325
|
+
blank();
|
|
326
|
+
section('VCARD');
|
|
327
|
+
blank();
|
|
328
|
+
for (const line of card.trim().split('\n')) print(` ${line}`);
|
|
329
|
+
blank();
|
|
330
|
+
if (copy && process.stdout.isTTY && copyToClipboard(card)) {
|
|
331
|
+
print(` ${green('copied')} vCard`);
|
|
332
|
+
}
|
|
169
333
|
};
|
|
170
334
|
|
|
171
335
|
export const printWork = () => {
|
|
172
|
-
|
|
336
|
+
blank();
|
|
337
|
+
section('CASE STUDIES');
|
|
338
|
+
blank();
|
|
339
|
+
print(` ${dim('Private production systems. I will walk through them on a call.')}`);
|
|
340
|
+
for (const item of cases) {
|
|
341
|
+
blank();
|
|
342
|
+
printCase(item, { deep: true });
|
|
343
|
+
}
|
|
344
|
+
blank();
|
|
345
|
+
print(` ${dim('mentorship')}`);
|
|
346
|
+
blank();
|
|
347
|
+
print(` ${bold(mentorship.who)} ${dim(mentorship.where)}`);
|
|
348
|
+
print(` ${mentorship.on.join(dim(' · '))}`);
|
|
349
|
+
for (const line of wrap(mentorship.still)) print(` ${line}`);
|
|
173
350
|
blank();
|
|
174
351
|
print(` ${dim('experience')}`);
|
|
175
352
|
blank();
|
|
@@ -181,13 +358,6 @@ export const printWork = () => {
|
|
|
181
358
|
print(` ${dim(job.stack.join(' · '))}`);
|
|
182
359
|
blank();
|
|
183
360
|
}
|
|
184
|
-
print(` ${dim('selected work')}`);
|
|
185
|
-
blank();
|
|
186
|
-
for (const item of work) {
|
|
187
|
-
print(` ${bold(item.title)} ${dim(item.company)}`);
|
|
188
|
-
for (const line of wrap(item.blurb)) print(` ${line}`);
|
|
189
|
-
blank();
|
|
190
|
-
}
|
|
191
361
|
print(` ${dim('people who were not bots')}`);
|
|
192
362
|
blank();
|
|
193
363
|
for (const quote of quotes) {
|
|
@@ -202,29 +372,35 @@ export const printSkills = () => {
|
|
|
202
372
|
section('STACK');
|
|
203
373
|
blank();
|
|
204
374
|
for (const group of skills) {
|
|
205
|
-
print(` ${bold(group.name)}`);
|
|
375
|
+
print(` ${bold(group.name)} ${dim(group.note)}`);
|
|
206
376
|
print(` ${group.items.join(dim(' · '))}`);
|
|
207
377
|
blank();
|
|
208
378
|
}
|
|
209
379
|
};
|
|
210
380
|
|
|
211
|
-
export const printHire = ({ copy = true } = {}) => {
|
|
381
|
+
export const printHire = ({ copy = true, report, to } = {}) => {
|
|
382
|
+
const mailto = mailtoFor({ report, to });
|
|
212
383
|
blank();
|
|
213
384
|
section('HIRE ME');
|
|
214
385
|
blank();
|
|
215
386
|
print(` ${dim('Skip the 47-field form. This is the whole application.')}`);
|
|
216
387
|
blank();
|
|
217
|
-
kv('Email', link(profile.email,
|
|
388
|
+
kv('Email', link(profile.email, mailto));
|
|
218
389
|
kv('Phone', link(profile.phone, `tel:${profile.phone.replace(/\s+/g, '')}`));
|
|
219
390
|
kv('Site', link(profile.website, profile.website));
|
|
220
391
|
kv('GitHub', link(profile.github, profile.github));
|
|
221
392
|
kv('LinkedIn', link(profile.linkedin, profile.linkedin));
|
|
222
393
|
kv('Resume', link(profile.resume, profile.resume));
|
|
223
394
|
blank();
|
|
395
|
+
if (report?.letter) {
|
|
396
|
+
print(` ${dim('last lint')} ${bold(report.letter.result)} ${dim(`${report.score}/100`)}`);
|
|
397
|
+
blank();
|
|
398
|
+
}
|
|
399
|
+
if (report?.fit) printFit(report.fit, { failed: !report.ok });
|
|
224
400
|
if (copy && process.stdout.isTTY && copyToClipboard(profile.email)) {
|
|
225
401
|
print(` ${green('copied')} ${profile.email} ${dim('to clipboard')}`);
|
|
226
402
|
}
|
|
227
|
-
print(` ${dim('or')} ${link('open a prefilled email',
|
|
403
|
+
print(` ${dim('or')} ${link('open a prefilled email', mailto)}`);
|
|
228
404
|
blank();
|
|
229
405
|
print(` ${dim('Remote senior / full-stack. I can start a conversation this week.')}`);
|
|
230
406
|
};
|
|
@@ -245,23 +421,44 @@ export const printHelp = () => {
|
|
|
245
421
|
print(banner());
|
|
246
422
|
blank();
|
|
247
423
|
print(` ${bold('npx jobless')} reject you, then the resume`);
|
|
248
|
-
print(` ${bold('npx jobless lint [file]')} lint a job posting
|
|
249
|
-
print(` ${bold('npx jobless
|
|
424
|
+
print(` ${bold('npx jobless lint [file]')} lint a job posting or jobs/`);
|
|
425
|
+
print(` ${bold('npx jobless lint --json')} findings + rejection letter`);
|
|
426
|
+
print(` ${bold('npx jobless lint --fix')} unified diff (dry-run)`);
|
|
427
|
+
print(` ${bold('npx jobless lint --write')} write the rewrite to the file`);
|
|
428
|
+
print(` ${bold('npx jobless lint --copy')} copy the company letter`);
|
|
429
|
+
print(` ${bold('npx jobless lint --rules')} print the rule table`);
|
|
430
|
+
print(` ${bold('npx jobless lint --format=github')} CI annotations`);
|
|
431
|
+
print(` ${bold('npx jobless work')} three case studies + timeline`);
|
|
432
|
+
print(` ${bold('npx jobless proof')} the 17-cron write-up`);
|
|
433
|
+
print(` ${bold('npx jobless card')} one-screen hire card`);
|
|
250
434
|
print(` ${bold('npx jobless hire')} email + links`);
|
|
435
|
+
print(` ${bold('npx jobless hire --to=manager')} hiring-manager subject`);
|
|
436
|
+
print(` ${bold('npx jobless vcf')} vCard`);
|
|
437
|
+
print(` ${bold('npx jobless why')} why this exists`);
|
|
438
|
+
print(` ${bold('npx jobless tweet')} 280 characters, no punchline`);
|
|
251
439
|
print(` ${bold('npx jobless who')} bio`);
|
|
252
440
|
print(` ${bold('npx jobless looking')} roles`);
|
|
253
441
|
print(` ${bold('npx jobless skills')} stack`);
|
|
254
442
|
print(` ${bold('npx jobless rant')} the market`);
|
|
255
443
|
print(` ${bold('npx jobless --resume')} skip the rejection`);
|
|
256
|
-
print(` ${bold('npx jobless --
|
|
444
|
+
print(` ${bold('npx jobless --seed=42')} same rejection every time`);
|
|
445
|
+
print(` ${bold('npx jobless --json')} machine readable resume`);
|
|
446
|
+
print(` ${bold('npx jobless --no-color')} recording-friendly`);
|
|
257
447
|
blank();
|
|
258
|
-
print(` ${dim('In the prompt:')} ${bold(green('h'))} ${dim('hires,')} ${bold('l')} ${dim('lints
|
|
448
|
+
print(` ${dim('In the prompt:')} ${bold(green('h'))} ${dim('hires,')} ${bold('l')} ${dim('lints,')} ${bold('c')} ${dim('copies the last letter,')} ${bold('q')} ${dim('quits.')}`);
|
|
449
|
+
print(` ${dim('Post the command, not the punchline:')} ${bold('npx jobless')}`);
|
|
259
450
|
print(` ${dim(profile.website)}`);
|
|
260
451
|
};
|
|
261
452
|
|
|
262
453
|
export const printLandingTeaser = () => {
|
|
263
454
|
blank();
|
|
264
|
-
print(` ${dim('Paste
|
|
455
|
+
print(` ${dim('Paste the posting that just rejected you:')} ${bold('l')}${dim('.')}`);
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
export const copyLetter = (letter) => {
|
|
459
|
+
const text = letterText(letter);
|
|
460
|
+
if (!text) return false;
|
|
461
|
+
return copyToClipboard(text);
|
|
265
462
|
};
|
|
266
463
|
|
|
267
464
|
export const toJson = () =>
|
|
@@ -284,6 +481,9 @@ export const toJson = () =>
|
|
|
284
481
|
lookingFor,
|
|
285
482
|
rants,
|
|
286
483
|
stats,
|
|
484
|
+
claims,
|
|
485
|
+
cases,
|
|
486
|
+
mentorship,
|
|
287
487
|
experience,
|
|
288
488
|
skills,
|
|
289
489
|
},
|