jobless 1.4.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 +41 -29
- package/package.json +3 -2
- package/src/cli.js +264 -58
- 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 +443 -18
- package/src/reject.js +89 -18
- package/src/screens.js +241 -43
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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(
|
|
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(
|
|
81
|
-
dim(`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'}`,
|
package/src/screens.js
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
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';
|
|
4
6
|
|
|
5
7
|
import {
|
|
6
8
|
about,
|
|
9
|
+
cases,
|
|
10
|
+
claims,
|
|
7
11
|
experience,
|
|
8
12
|
hireMailto,
|
|
9
13
|
lookingFor,
|
|
14
|
+
mailtoFor,
|
|
10
15
|
menu,
|
|
16
|
+
mentorship,
|
|
11
17
|
profile,
|
|
12
18
|
quotes,
|
|
13
19
|
rants,
|
|
14
20
|
signature,
|
|
15
21
|
skills,
|
|
16
22
|
stats,
|
|
17
|
-
|
|
23
|
+
vcard,
|
|
18
24
|
} from './data.js';
|
|
19
25
|
import {
|
|
20
26
|
banner,
|
|
@@ -37,23 +43,6 @@ import {
|
|
|
37
43
|
yellow,
|
|
38
44
|
} from './style.js';
|
|
39
45
|
|
|
40
|
-
const copyToClipboard = (text) => {
|
|
41
|
-
try {
|
|
42
|
-
if (process.platform === 'darwin') {
|
|
43
|
-
execFileSync('pbcopy', { input: text });
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
if (process.platform === 'win32') {
|
|
47
|
-
execFileSync('clip', { input: text, windowsHide: true });
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
execFileSync('xclip', ['-selection', 'clipboard'], { input: text });
|
|
51
|
-
return true;
|
|
52
|
-
} catch {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
46
|
export const printHeader = () => {
|
|
58
47
|
print(banner());
|
|
59
48
|
blank();
|
|
@@ -86,7 +75,7 @@ export const printSignature = () => {
|
|
|
86
75
|
export const printHints = () => {
|
|
87
76
|
blank();
|
|
88
77
|
print(
|
|
89
|
-
` ${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`,
|
|
90
79
|
);
|
|
91
80
|
};
|
|
92
81
|
|
|
@@ -101,6 +90,15 @@ export const printWho = () => {
|
|
|
101
90
|
kv('Now', profile.currently);
|
|
102
91
|
kv('Study', profile.study);
|
|
103
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
|
+
}
|
|
104
102
|
};
|
|
105
103
|
|
|
106
104
|
export const printLooking = () => {
|
|
@@ -138,39 +136,217 @@ export const printRant = () => {
|
|
|
138
136
|
print(` ${dim('If you want the work, press')} ${bold('1')}${dim('. If you want to hire, press')} ${bold(green('h'))}${dim('.')}`);
|
|
139
137
|
};
|
|
140
138
|
|
|
141
|
-
export const printLintReport = (report) => {
|
|
142
|
-
const { findings, source, errors, warnings, letter, ok } = report;
|
|
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
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
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
167
|
printCompanyReject(letter);
|
|
168
|
+
if (report.fit) printFit(report.fit, { failed: true });
|
|
165
169
|
blank();
|
|
166
170
|
print(
|
|
167
|
-
` ${errors} error${errors === 1 ? '' : 's'} ${warnings} warning${warnings === 1 ? '' : 's'} ${dim('·')} ${dim('hire
|
|
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')}`,
|
|
168
172
|
);
|
|
173
|
+
if (copied) {
|
|
174
|
+
print(` ${green('copied')} rejection letter`);
|
|
175
|
+
}
|
|
169
176
|
return report;
|
|
170
177
|
};
|
|
171
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
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
172
335
|
export const printWork = () => {
|
|
173
|
-
|
|
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}`);
|
|
174
350
|
blank();
|
|
175
351
|
print(` ${dim('experience')}`);
|
|
176
352
|
blank();
|
|
@@ -182,13 +358,6 @@ export const printWork = () => {
|
|
|
182
358
|
print(` ${dim(job.stack.join(' · '))}`);
|
|
183
359
|
blank();
|
|
184
360
|
}
|
|
185
|
-
print(` ${dim('selected work')}`);
|
|
186
|
-
blank();
|
|
187
|
-
for (const item of work) {
|
|
188
|
-
print(` ${bold(item.title)} ${dim(item.company)}`);
|
|
189
|
-
for (const line of wrap(item.blurb)) print(` ${line}`);
|
|
190
|
-
blank();
|
|
191
|
-
}
|
|
192
361
|
print(` ${dim('people who were not bots')}`);
|
|
193
362
|
blank();
|
|
194
363
|
for (const quote of quotes) {
|
|
@@ -203,29 +372,35 @@ export const printSkills = () => {
|
|
|
203
372
|
section('STACK');
|
|
204
373
|
blank();
|
|
205
374
|
for (const group of skills) {
|
|
206
|
-
print(` ${bold(group.name)}`);
|
|
375
|
+
print(` ${bold(group.name)} ${dim(group.note)}`);
|
|
207
376
|
print(` ${group.items.join(dim(' · '))}`);
|
|
208
377
|
blank();
|
|
209
378
|
}
|
|
210
379
|
};
|
|
211
380
|
|
|
212
|
-
export const printHire = ({ copy = true } = {}) => {
|
|
381
|
+
export const printHire = ({ copy = true, report, to } = {}) => {
|
|
382
|
+
const mailto = mailtoFor({ report, to });
|
|
213
383
|
blank();
|
|
214
384
|
section('HIRE ME');
|
|
215
385
|
blank();
|
|
216
386
|
print(` ${dim('Skip the 47-field form. This is the whole application.')}`);
|
|
217
387
|
blank();
|
|
218
|
-
kv('Email', link(profile.email,
|
|
388
|
+
kv('Email', link(profile.email, mailto));
|
|
219
389
|
kv('Phone', link(profile.phone, `tel:${profile.phone.replace(/\s+/g, '')}`));
|
|
220
390
|
kv('Site', link(profile.website, profile.website));
|
|
221
391
|
kv('GitHub', link(profile.github, profile.github));
|
|
222
392
|
kv('LinkedIn', link(profile.linkedin, profile.linkedin));
|
|
223
393
|
kv('Resume', link(profile.resume, profile.resume));
|
|
224
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 });
|
|
225
400
|
if (copy && process.stdout.isTTY && copyToClipboard(profile.email)) {
|
|
226
401
|
print(` ${green('copied')} ${profile.email} ${dim('to clipboard')}`);
|
|
227
402
|
}
|
|
228
|
-
print(` ${dim('or')} ${link('open a prefilled email',
|
|
403
|
+
print(` ${dim('or')} ${link('open a prefilled email', mailto)}`);
|
|
229
404
|
blank();
|
|
230
405
|
print(` ${dim('Remote senior / full-stack. I can start a conversation this week.')}`);
|
|
231
406
|
};
|
|
@@ -246,24 +421,44 @@ export const printHelp = () => {
|
|
|
246
421
|
print(banner());
|
|
247
422
|
blank();
|
|
248
423
|
print(` ${bold('npx jobless')} reject you, then the resume`);
|
|
249
|
-
print(` ${bold('npx jobless lint [file]')} lint a job posting
|
|
424
|
+
print(` ${bold('npx jobless lint [file]')} lint a job posting or jobs/`);
|
|
250
425
|
print(` ${bold('npx jobless lint --json')} findings + rejection letter`);
|
|
251
|
-
print(` ${bold('npx jobless
|
|
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`);
|
|
252
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`);
|
|
253
439
|
print(` ${bold('npx jobless who')} bio`);
|
|
254
440
|
print(` ${bold('npx jobless looking')} roles`);
|
|
255
441
|
print(` ${bold('npx jobless skills')} stack`);
|
|
256
442
|
print(` ${bold('npx jobless rant')} the market`);
|
|
257
443
|
print(` ${bold('npx jobless --resume')} skip the rejection`);
|
|
444
|
+
print(` ${bold('npx jobless --seed=42')} same rejection every time`);
|
|
258
445
|
print(` ${bold('npx jobless --json')} machine readable resume`);
|
|
446
|
+
print(` ${bold('npx jobless --no-color')} recording-friendly`);
|
|
259
447
|
blank();
|
|
260
|
-
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')}`);
|
|
261
450
|
print(` ${dim(profile.website)}`);
|
|
262
451
|
};
|
|
263
452
|
|
|
264
453
|
export const printLandingTeaser = () => {
|
|
265
454
|
blank();
|
|
266
|
-
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);
|
|
267
462
|
};
|
|
268
463
|
|
|
269
464
|
export const toJson = () =>
|
|
@@ -286,6 +481,9 @@ export const toJson = () =>
|
|
|
286
481
|
lookingFor,
|
|
287
482
|
rants,
|
|
288
483
|
stats,
|
|
484
|
+
claims,
|
|
485
|
+
cases,
|
|
486
|
+
mentorship,
|
|
289
487
|
experience,
|
|
290
488
|
skills,
|
|
291
489
|
},
|