jobless 1.5.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.
- package/README.md +103 -49
- package/package.json +2 -2
- package/src/cli.js +60 -361
- package/src/data.js +12 -158
- package/src/fit.js +1 -1
- package/src/hiring.js +70 -0
- package/src/identity.js +5 -1
- package/src/interactive.js +73 -0
- package/src/lint-cli.js +128 -0
- package/src/lint-screens.js +114 -0
- package/src/profile-screens.js +248 -0
- package/src/screens.js +20 -337
- package/src/style.js +16 -8
- package/src/work-data.js +152 -0
|
@@ -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/screens.js
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
import { copyToClipboard } from './clip.js';
|
|
2
|
-
import { unifiedDiff } from './diff.js';
|
|
3
2
|
import { tweetFromReport } from './fit.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import { letterText } from './reject.js';
|
|
4
|
+
export * from './profile-screens.js';
|
|
5
|
+
export * from './lint-screens.js';
|
|
7
6
|
import {
|
|
8
|
-
about,
|
|
9
7
|
cases,
|
|
10
8
|
claims,
|
|
11
9
|
experience,
|
|
12
10
|
hireMailto,
|
|
13
11
|
lookingFor,
|
|
14
|
-
mailtoFor,
|
|
15
12
|
menu,
|
|
16
13
|
mentorship,
|
|
17
14
|
profile,
|
|
18
|
-
quotes,
|
|
19
15
|
rants,
|
|
20
16
|
signature,
|
|
21
17
|
skills,
|
|
22
18
|
stats,
|
|
23
|
-
vcard,
|
|
24
19
|
} from './data.js';
|
|
25
20
|
import {
|
|
26
21
|
banner,
|
|
@@ -32,46 +27,13 @@ import {
|
|
|
32
27
|
dim,
|
|
33
28
|
green,
|
|
34
29
|
inner,
|
|
35
|
-
kv,
|
|
36
|
-
link,
|
|
37
|
-
magenta,
|
|
38
30
|
pad,
|
|
39
31
|
print,
|
|
40
|
-
red,
|
|
41
32
|
section,
|
|
42
33
|
wrap,
|
|
43
34
|
yellow,
|
|
44
35
|
} from './style.js';
|
|
45
36
|
|
|
46
|
-
export const printHeader = () => {
|
|
47
|
-
print(banner());
|
|
48
|
-
blank();
|
|
49
|
-
print(` ${bold(cyan(profile.name))}`);
|
|
50
|
-
print(` ${profile.title}`);
|
|
51
|
-
print(` ${dim(`${profile.location} · ${profile.availability}`)}`);
|
|
52
|
-
blank();
|
|
53
|
-
const statLine = (items) =>
|
|
54
|
-
items.map((item) => `${bold(green(item.value))} ${dim(item.label)}`).join(dim(' '));
|
|
55
|
-
print(` ${statLine(stats.slice(0, 2))}`);
|
|
56
|
-
print(` ${statLine(stats.slice(2))}`);
|
|
57
|
-
blank();
|
|
58
|
-
print(
|
|
59
|
-
` ${link(profile.email, hireMailto)}${dim(' · ')}${link(profile.website, profile.website)}${dim(' · ')}${link(
|
|
60
|
-
profile.github.replace('https://', ''),
|
|
61
|
-
profile.github,
|
|
62
|
-
)}`,
|
|
63
|
-
);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export const printSignature = () => {
|
|
67
|
-
blank();
|
|
68
|
-
section('PROOF');
|
|
69
|
-
blank();
|
|
70
|
-
print(` ${bold(signature.title)}`);
|
|
71
|
-
blank();
|
|
72
|
-
for (const line of signature.lines) bullet(line);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
37
|
export const printHints = () => {
|
|
76
38
|
blank();
|
|
77
39
|
print(
|
|
@@ -79,51 +41,12 @@ export const printHints = () => {
|
|
|
79
41
|
);
|
|
80
42
|
};
|
|
81
43
|
|
|
82
|
-
export const printWho = () => {
|
|
83
|
-
blank();
|
|
84
|
-
section('WHO');
|
|
85
|
-
blank();
|
|
86
|
-
for (const paragraph of about) {
|
|
87
|
-
bullet(paragraph);
|
|
88
|
-
blank();
|
|
89
|
-
}
|
|
90
|
-
kv('Now', profile.currently);
|
|
91
|
-
kv('Study', profile.study);
|
|
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
|
-
};
|
|
103
|
-
|
|
104
|
-
export const printLooking = () => {
|
|
105
|
-
blank();
|
|
106
|
-
section('WHAT I WANT');
|
|
107
|
-
blank();
|
|
108
|
-
print(` ${dim('roles')}`);
|
|
109
|
-
for (const role of lookingFor.roles) bullet(role, green);
|
|
110
|
-
blank();
|
|
111
|
-
print(` ${dim('setup')}`);
|
|
112
|
-
for (const item of lookingFor.setup) bullet(item);
|
|
113
|
-
blank();
|
|
114
|
-
print(` ${dim('yes')}`);
|
|
115
|
-
for (const item of lookingFor.wants) bullet(item);
|
|
116
|
-
blank();
|
|
117
|
-
print(` ${dim('hard no')}`);
|
|
118
|
-
for (const item of lookingFor.no) bullet(item, yellow);
|
|
119
|
-
};
|
|
120
|
-
|
|
121
44
|
export const printRant = () => {
|
|
122
45
|
blank();
|
|
123
46
|
section('THE MARKET');
|
|
124
47
|
blank();
|
|
125
48
|
for (const line of wrap(
|
|
126
|
-
'
|
|
49
|
+
'A hiring incident report, with coping mechanisms.',
|
|
127
50
|
)) {
|
|
128
51
|
print(` ${dim(line)}`);
|
|
129
52
|
}
|
|
@@ -136,170 +59,11 @@ export const printRant = () => {
|
|
|
136
59
|
print(` ${dim('If you want the work, press')} ${bold('1')}${dim('. If you want to hire, press')} ${bold(green('h'))}${dim('.')}`);
|
|
137
60
|
};
|
|
138
61
|
|
|
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;
|
|
142
|
-
|
|
143
|
-
blank();
|
|
144
|
-
section('JOBLESS LINT');
|
|
145
|
-
blank();
|
|
146
|
-
print(` ${dim(source)} ${dim('·')} ${bold(`${score}/100`)}`);
|
|
147
|
-
blank();
|
|
148
|
-
|
|
149
|
-
if (ok && !findings.length) {
|
|
150
|
-
print(` ${green('clean')} this posting might be a real job.`);
|
|
151
|
-
if (report.fit) printFit(report.fit, { failed: false });
|
|
152
|
-
blank();
|
|
153
|
-
print(` ${dim('If you still want the engineer who wrote the linter:')} ${bold(green('h'))}`);
|
|
154
|
-
return report;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
for (const finding of visible) {
|
|
158
|
-
const tag = finding.level === 'error' ? red('error') : yellow('warn ');
|
|
159
|
-
print(` ${tag} ${finding.message}`);
|
|
160
|
-
print(` ${dim(finding.id)}`);
|
|
161
|
-
if (finding.line && finding.snippet) {
|
|
162
|
-
print(` ${dim(`L${finding.line}`)} ${finding.snippet}`);
|
|
163
|
-
}
|
|
164
|
-
blank();
|
|
165
|
-
}
|
|
166
|
-
|
|
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
62
|
export const printTweet = (report) => {
|
|
299
63
|
blank();
|
|
300
64
|
print(tweetFromReport(report));
|
|
301
65
|
blank();
|
|
302
|
-
print(` ${dim('
|
|
66
|
+
print(` ${dim('Copy it, remix it, send it to someone waiting for a reply.')}`);
|
|
303
67
|
};
|
|
304
68
|
|
|
305
69
|
export const printWhy = () => {
|
|
@@ -307,12 +71,12 @@ export const printWhy = () => {
|
|
|
307
71
|
section('WHY');
|
|
308
72
|
blank();
|
|
309
73
|
for (const line of [
|
|
310
|
-
'
|
|
311
|
-
'
|
|
312
|
-
'
|
|
313
|
-
'
|
|
314
|
-
'
|
|
315
|
-
'
|
|
74
|
+
'I got invited to interview and rejected at the same time.',
|
|
75
|
+
'So I turned the hiring process into an npm package.',
|
|
76
|
+
'They asked how I handle rejection. Apparently: semantic versioning.',
|
|
77
|
+
'The second act is a reverse ATS: lint the posting, then reject it yourself.',
|
|
78
|
+
'The hiring simulator is satire. The engineer behind it is real.',
|
|
79
|
+
'Try npx jobless lint --demo. Meet me with npx jobless hire.',
|
|
316
80
|
]) {
|
|
317
81
|
bullet(line);
|
|
318
82
|
}
|
|
@@ -320,91 +84,6 @@ export const printWhy = () => {
|
|
|
320
84
|
print(` ${dim('Hire is')} ${bold(green('h'))}${dim('.')}`);
|
|
321
85
|
};
|
|
322
86
|
|
|
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
|
-
|
|
335
|
-
export const printWork = () => {
|
|
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}`);
|
|
350
|
-
blank();
|
|
351
|
-
print(` ${dim('experience')}`);
|
|
352
|
-
blank();
|
|
353
|
-
for (const job of experience) {
|
|
354
|
-
print(` ${bold(job.role)}`);
|
|
355
|
-
print(` ${cyan(job.company)} ${dim(`${job.dates} · ${job.place}`)}`);
|
|
356
|
-
blank();
|
|
357
|
-
for (const point of job.points) bullet(point);
|
|
358
|
-
print(` ${dim(job.stack.join(' · '))}`);
|
|
359
|
-
blank();
|
|
360
|
-
}
|
|
361
|
-
print(` ${dim('people who were not bots')}`);
|
|
362
|
-
blank();
|
|
363
|
-
for (const quote of quotes) {
|
|
364
|
-
print(` ${magenta(`"${quote.text}"`)}`);
|
|
365
|
-
print(` ${dim(`— ${quote.name}${quote.role ? `, ${quote.role}` : ''}`)}`);
|
|
366
|
-
blank();
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
export const printSkills = () => {
|
|
371
|
-
blank();
|
|
372
|
-
section('STACK');
|
|
373
|
-
blank();
|
|
374
|
-
for (const group of skills) {
|
|
375
|
-
print(` ${bold(group.name)} ${dim(group.note)}`);
|
|
376
|
-
print(` ${group.items.join(dim(' · '))}`);
|
|
377
|
-
blank();
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
export const printHire = ({ copy = true, report, to } = {}) => {
|
|
382
|
-
const mailto = mailtoFor({ report, to });
|
|
383
|
-
blank();
|
|
384
|
-
section('HIRE ME');
|
|
385
|
-
blank();
|
|
386
|
-
print(` ${dim('Skip the 47-field form. This is the whole application.')}`);
|
|
387
|
-
blank();
|
|
388
|
-
kv('Email', link(profile.email, mailto));
|
|
389
|
-
kv('Phone', link(profile.phone, `tel:${profile.phone.replace(/\s+/g, '')}`));
|
|
390
|
-
kv('Site', link(profile.website, profile.website));
|
|
391
|
-
kv('GitHub', link(profile.github, profile.github));
|
|
392
|
-
kv('LinkedIn', link(profile.linkedin, profile.linkedin));
|
|
393
|
-
kv('Resume', link(profile.resume, profile.resume));
|
|
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 });
|
|
400
|
-
if (copy && process.stdout.isTTY && copyToClipboard(profile.email)) {
|
|
401
|
-
print(` ${green('copied')} ${profile.email} ${dim('to clipboard')}`);
|
|
402
|
-
}
|
|
403
|
-
print(` ${dim('or')} ${link('open a prefilled email', mailto)}`);
|
|
404
|
-
blank();
|
|
405
|
-
print(` ${dim('Remote senior / full-stack. I can start a conversation this week.')}`);
|
|
406
|
-
};
|
|
407
|
-
|
|
408
87
|
export const printMenu = () => {
|
|
409
88
|
blank();
|
|
410
89
|
print(
|
|
@@ -420,7 +99,11 @@ export const printMenu = () => {
|
|
|
420
99
|
export const printHelp = () => {
|
|
421
100
|
print(banner());
|
|
422
101
|
blank();
|
|
423
|
-
print(` ${bold('npx jobless')}
|
|
102
|
+
print(` ${bold('npx jobless')} reproduce a hiring race condition`);
|
|
103
|
+
print(` ${bold('npx jobless share --copy')} copy a compact anonymous result`);
|
|
104
|
+
print(` ${bold('npx jobless --record')} repeatable, anonymous, no prompt`);
|
|
105
|
+
print(` ${bold('npx jobless --personalize')} opt in to your local Git name`);
|
|
106
|
+
print(` ${bold('npx jobless lint --demo')} reject a fictional posting`);
|
|
424
107
|
print(` ${bold('npx jobless lint [file]')} lint a job posting or jobs/`);
|
|
425
108
|
print(` ${bold('npx jobless lint --json')} findings + rejection letter`);
|
|
426
109
|
print(` ${bold('npx jobless lint --fix')} unified diff (dry-run)`);
|
|
@@ -435,17 +118,17 @@ export const printHelp = () => {
|
|
|
435
118
|
print(` ${bold('npx jobless hire --to=manager')} hiring-manager subject`);
|
|
436
119
|
print(` ${bold('npx jobless vcf')} vCard`);
|
|
437
120
|
print(` ${bold('npx jobless why')} why this exists`);
|
|
438
|
-
print(` ${bold('npx jobless tweet')}
|
|
121
|
+
print(` ${bold('npx jobless tweet')} the origin story, ready to share`);
|
|
439
122
|
print(` ${bold('npx jobless who')} bio`);
|
|
440
123
|
print(` ${bold('npx jobless looking')} roles`);
|
|
441
124
|
print(` ${bold('npx jobless skills')} stack`);
|
|
442
125
|
print(` ${bold('npx jobless rant')} the market`);
|
|
443
126
|
print(` ${bold('npx jobless --resume')} skip the rejection`);
|
|
444
|
-
print(` ${bold('npx jobless --seed=42')}
|
|
127
|
+
print(` ${bold('npx jobless --seed=42')} choose a repeatable scenario`);
|
|
445
128
|
print(` ${bold('npx jobless --json')} machine readable resume`);
|
|
446
|
-
print(` ${bold('npx jobless --no-color')}
|
|
129
|
+
print(` ${bold('npx jobless --no-color')} plain text output`);
|
|
447
130
|
blank();
|
|
448
|
-
print(` ${dim('In the prompt:')} ${bold(green('h'))} ${dim('hires,')} ${bold('l')} ${dim('lints,')} ${bold('c')} ${dim('copies the last
|
|
131
|
+
print(` ${dim('In the prompt:')} ${bold(green('h'))} ${dim('hires,')} ${bold('l')} ${dim('lints,')} ${bold('c')} ${dim('copies the last result,')} ${bold('q')} ${dim('quits.')}`);
|
|
449
132
|
print(` ${dim('Post the command, not the punchline:')} ${bold('npx jobless')}`);
|
|
450
133
|
print(` ${dim(profile.website)}`);
|
|
451
134
|
};
|