job-pro 1.0.14 → 1.0.16
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/dist/apply.js +17 -0
- package/dist/index.js +89 -3
- package/package.json +1 -1
package/dist/apply.js
CHANGED
|
@@ -59,6 +59,23 @@ const TEMPLATE = {
|
|
|
59
59
|
// nationality: "China",
|
|
60
60
|
},
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Read profile.json as-is, returning whatever is there.
|
|
64
|
+
* Skips the loadProfile() validation so callers (like `profile lint`)
|
|
65
|
+
* can inspect partial / broken profiles instead of getting a flat fail.
|
|
66
|
+
*/
|
|
67
|
+
export function loadProfileRaw() {
|
|
68
|
+
if (!existsSync(PROFILE_PATH)) {
|
|
69
|
+
return { ok: false, path: PROFILE_PATH, message: `profile not found at ${PROFILE_PATH}` };
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const raw = readFileSync(PROFILE_PATH, "utf8");
|
|
73
|
+
return { ok: true, path: PROFILE_PATH, profile: JSON.parse(raw) };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return { ok: false, path: PROFILE_PATH, message: `could not parse ${PROFILE_PATH}: ${err instanceof Error ? err.message : err}` };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
62
79
|
export function loadProfile() {
|
|
63
80
|
if (!existsSync(PROFILE_PATH)) {
|
|
64
81
|
return {
|
package/dist/index.js
CHANGED
|
@@ -50,7 +50,7 @@ import * as geely from "./geely.js";
|
|
|
50
50
|
import * as webank from "./webank.js";
|
|
51
51
|
import * as horizonrobotics from "./horizonrobotics.js";
|
|
52
52
|
import * as cambricon from "./cambricon.js";
|
|
53
|
-
import { loadProfile, loadSession, profileTemplate, saveProfile, stageApplication, submitApplication, executeFeishu3Step, executeMokaApply, executeBeisenWecruit, executeBeisenITalent, executeCdpRealBrowser, buildFormTemplate, applyFormFile, promptUnansweredFields, formatStaged, } from "./apply.js";
|
|
53
|
+
import { loadProfile, loadProfileRaw, loadSession, profileTemplate, saveProfile, stageApplication, submitApplication, executeFeishu3Step, executeMokaApply, executeBeisenWecruit, executeBeisenITalent, executeCdpRealBrowser, buildFormTemplate, applyFormFile, promptUnansweredFields, formatStaged, } from "./apply.js";
|
|
54
54
|
import { createInterface } from "node:readline";
|
|
55
55
|
import { memoryList, memoryGet, memorySet, memoryEvent, memoryClear, } from "./memory.js";
|
|
56
56
|
import { writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
@@ -125,6 +125,8 @@ USAGE
|
|
|
125
125
|
write ~/.jobpro/profile.json
|
|
126
126
|
--interactive fills it via prompts.
|
|
127
127
|
job-pro profile show print the loaded profile
|
|
128
|
+
job-pro profile lint validate format of every field
|
|
129
|
+
(exits 1 on any FAIL — scriptable)
|
|
128
130
|
job-pro find <keyword> search ALL 50 companies in parallel
|
|
129
131
|
[--limit N] [--companies a,b,c]
|
|
130
132
|
[--timeout ms] [--apply-ready]
|
|
@@ -165,6 +167,7 @@ VERBS (same surface for every company)
|
|
|
165
167
|
pass "-" to read resume from stdin
|
|
166
168
|
resume-check <resume-text-or--> structural sanity check on a resume
|
|
167
169
|
apply <post_id> stage an application (Phase 2 dry-run)
|
|
170
|
+
--schema dump raw schema (no profile needed)
|
|
168
171
|
--print-form emit a fillable JSON template
|
|
169
172
|
--form-file <path> merge per-job answers
|
|
170
173
|
--interactive prompt for unanswered fields
|
|
@@ -421,6 +424,7 @@ async function runCompany(adapter, company, rawArgs) {
|
|
|
421
424
|
if (verb === "apply") {
|
|
422
425
|
const reallySubmit = args.includes("--really-submit");
|
|
423
426
|
const printForm = args.includes("--print-form");
|
|
427
|
+
const schemaOnly = args.includes("--schema");
|
|
424
428
|
const interactive = args.includes("--interactive");
|
|
425
429
|
const remember = args.includes("--remember");
|
|
426
430
|
const { args: aDebug, value: debugUrl } = popFlagValue(args, "--debug-submit-to");
|
|
@@ -507,7 +511,7 @@ async function runCompany(adapter, company, rawArgs) {
|
|
|
507
511
|
void aBatch;
|
|
508
512
|
const postId = args[0];
|
|
509
513
|
if (!postId)
|
|
510
|
-
die(`usage: job-pro ${company} apply <post_id> [--print-form | --form-file <path> | --interactive [--remember] | --batch <file>] [--debug-submit-to <url> | --really-submit]`);
|
|
514
|
+
die(`usage: job-pro ${company} apply <post_id> [--schema | --print-form | --form-file <path> | --interactive [--remember] | --batch <file>] [--debug-submit-to <url> | --really-submit]`);
|
|
511
515
|
const fetchSchema = adapter.fetchApplicationSchema;
|
|
512
516
|
if (typeof fetchSchema !== "function") {
|
|
513
517
|
return emit({
|
|
@@ -528,6 +532,11 @@ async function runCompany(adapter, company, rawArgs) {
|
|
|
528
532
|
if (!sr.ok || !sr.schema) {
|
|
529
533
|
return emit({ ok: false, source: company, post_id: postId, message: sr.message ?? "unknown error" }, compact);
|
|
530
534
|
}
|
|
535
|
+
// --schema short-circuits everything (and crucially doesn't need a
|
|
536
|
+
// profile). Useful for recon: "what fields does this job ask?".
|
|
537
|
+
if (schemaOnly) {
|
|
538
|
+
return emit({ ok: true, source: company, post_id: postId, schema: sr.schema }, compact);
|
|
539
|
+
}
|
|
531
540
|
const prof = loadProfile();
|
|
532
541
|
if (!prof.ok) {
|
|
533
542
|
return emit({
|
|
@@ -1231,7 +1240,84 @@ async function main() {
|
|
|
1231
1240
|
console.log(JSON.stringify(r.profile, null, 2));
|
|
1232
1241
|
return;
|
|
1233
1242
|
}
|
|
1234
|
-
|
|
1243
|
+
if (sub === "lint") {
|
|
1244
|
+
const compact = args.includes("--compact");
|
|
1245
|
+
const r = loadProfileRaw();
|
|
1246
|
+
if (!r.ok) {
|
|
1247
|
+
console.error(r.message);
|
|
1248
|
+
process.exit(1);
|
|
1249
|
+
}
|
|
1250
|
+
const p = r.profile;
|
|
1251
|
+
const findings = [];
|
|
1252
|
+
// first_name / last_name
|
|
1253
|
+
for (const k of ["first_name", "last_name"]) {
|
|
1254
|
+
const v = (p[k] ?? "").trim();
|
|
1255
|
+
if (!v)
|
|
1256
|
+
findings.push({ level: "FAIL", check: k, message: "missing" });
|
|
1257
|
+
else
|
|
1258
|
+
findings.push({ level: "PASS", check: k, message: v });
|
|
1259
|
+
}
|
|
1260
|
+
// email
|
|
1261
|
+
const email = (p.email ?? "").trim();
|
|
1262
|
+
if (!email)
|
|
1263
|
+
findings.push({ level: "FAIL", check: "email", message: "missing" });
|
|
1264
|
+
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
|
|
1265
|
+
findings.push({ level: "FAIL", check: "email", message: `"${email}" doesn't look like a valid address` });
|
|
1266
|
+
else
|
|
1267
|
+
findings.push({ level: "PASS", check: "email", message: email });
|
|
1268
|
+
// phone
|
|
1269
|
+
const phone = (p.phone ?? "").trim();
|
|
1270
|
+
if (!phone)
|
|
1271
|
+
findings.push({ level: "FAIL", check: "phone", message: "missing" });
|
|
1272
|
+
else {
|
|
1273
|
+
const digitCount = phone.replace(/\D/g, "").length;
|
|
1274
|
+
if (digitCount < 7)
|
|
1275
|
+
findings.push({ level: "FAIL", check: "phone", message: `"${phone}" has ${digitCount} digit(s); need 7+` });
|
|
1276
|
+
else if (!phone.startsWith("+"))
|
|
1277
|
+
findings.push({ level: "WARN", check: "phone", message: `"${phone}" missing country code (recommended for non-anon adapters; e.g. +86 / +1)` });
|
|
1278
|
+
else
|
|
1279
|
+
findings.push({ level: "PASS", check: "phone", message: phone });
|
|
1280
|
+
}
|
|
1281
|
+
// resume_path
|
|
1282
|
+
const rp = (p.resume_path ?? "").trim();
|
|
1283
|
+
if (!rp)
|
|
1284
|
+
findings.push({ level: "FAIL", check: "resume_path", message: "missing" });
|
|
1285
|
+
else if (!existsSync(rp))
|
|
1286
|
+
findings.push({ level: "FAIL", check: "resume_path", message: `file not found: ${rp}` });
|
|
1287
|
+
else {
|
|
1288
|
+
const lower = rp.toLowerCase();
|
|
1289
|
+
if (!/\.(pdf|docx?|md|txt|rtf)$/i.test(lower))
|
|
1290
|
+
findings.push({ level: "WARN", check: "resume_path", message: `unusual extension: ${rp} (most ATS expect .pdf or .docx)` });
|
|
1291
|
+
else
|
|
1292
|
+
findings.push({ level: "PASS", check: "resume_path", message: rp });
|
|
1293
|
+
}
|
|
1294
|
+
// custom
|
|
1295
|
+
const customCount = Object.keys(p.custom ?? {}).length;
|
|
1296
|
+
if (customCount > 0) {
|
|
1297
|
+
const emptyValues = Object.entries(p.custom ?? {})
|
|
1298
|
+
.filter(([, v]) => typeof v !== "string" || v.trim() === "")
|
|
1299
|
+
.map(([k]) => k);
|
|
1300
|
+
if (emptyValues.length > 0)
|
|
1301
|
+
findings.push({ level: "WARN", check: "custom", message: `${emptyValues.length} empty value(s): ${emptyValues.slice(0, 5).join(", ")}` });
|
|
1302
|
+
else
|
|
1303
|
+
findings.push({ level: "PASS", check: "custom", message: `${customCount} answer(s)` });
|
|
1304
|
+
}
|
|
1305
|
+
const fails = findings.filter((f) => f.level === "FAIL").length;
|
|
1306
|
+
const warns = findings.filter((f) => f.level === "WARN").length;
|
|
1307
|
+
if (compact) {
|
|
1308
|
+
console.log(JSON.stringify({ ok: fails === 0, fails, warns, findings }));
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
const ICON = { PASS: "✓", WARN: "!", FAIL: "✗" };
|
|
1312
|
+
for (const f of findings)
|
|
1313
|
+
console.log(` ${ICON[f.level]} ${f.check.padEnd(13)} ${f.message}`);
|
|
1314
|
+
console.log(`\n ${fails} fail / ${warns} warn / ${findings.length - fails - warns} pass`);
|
|
1315
|
+
}
|
|
1316
|
+
if (fails > 0)
|
|
1317
|
+
process.exit(1);
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
die(`usage: job-pro profile <init [--interactive] [--force] | show | lint>`);
|
|
1235
1321
|
}
|
|
1236
1322
|
const adapter = ADAPTERS[cmd];
|
|
1237
1323
|
if (adapter) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job-pro",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "Query Chinese big-tech campus recruiting from your terminal. 50 companies, all 50 live. 46 via each company's own API; the 4 with no public canonical feed (Hikvision, CICC, Cainiao, WeBank) surfaced via Liepin as a clearly-labeled third-party fallback. No signup, no token, no server.",
|
|
5
5
|
"homepage": "https://job.ha7ch.com",
|
|
6
6
|
"repository": "https://github.com/HA7CH/job-pro",
|