job-pro 1.0.41 → 1.0.43
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/index.js +121 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -143,6 +143,9 @@ USAGE
|
|
|
143
143
|
job-pro list [--compact] list all 50 companies + source family
|
|
144
144
|
job-pro status [--compact] survey profile / sessions / memory / chrome
|
|
145
145
|
job-pro selftest [--compact] end-to-end check: search → schema → echo-submit
|
|
146
|
+
job-pro recon [--companies a,b,c] probe every adapter's submit_endpoint
|
|
147
|
+
(classifies as verified-real / 404 /
|
|
148
|
+
html-fallthrough / external)
|
|
146
149
|
job-pro profile init [--interactive] [--force]
|
|
147
150
|
write ~/.jobpro/profile.json
|
|
148
151
|
--interactive fills it via prompts.
|
|
@@ -1212,6 +1215,124 @@ async function main() {
|
|
|
1212
1215
|
printStatus(compact);
|
|
1213
1216
|
return;
|
|
1214
1217
|
}
|
|
1218
|
+
if (cmd === "recon") {
|
|
1219
|
+
// Probe every adapter's submit_endpoint anonymously and classify the
|
|
1220
|
+
// response. Catches upstream URL drift (the path went 404 because
|
|
1221
|
+
// upstream renamed it) and is the same probe routine I used by hand
|
|
1222
|
+
// to populate endpoint_verified for the 15 verified adapters.
|
|
1223
|
+
const compact = args.includes("--compact");
|
|
1224
|
+
const { args: aCompanies, value: companiesStr } = popFlagValue(args, "--companies");
|
|
1225
|
+
void aCompanies;
|
|
1226
|
+
const scope = companiesStr
|
|
1227
|
+
? companiesStr.split(",").map((s) => s.trim()).filter(Boolean)
|
|
1228
|
+
: Object.keys(ADAPTERS);
|
|
1229
|
+
function classify(status, body, contentType) {
|
|
1230
|
+
const isHTML = contentType.includes("html") || body.trim().startsWith("<");
|
|
1231
|
+
if (status === 404)
|
|
1232
|
+
return isHTML ? "html-fallthrough" : "speculative-404";
|
|
1233
|
+
if (isHTML)
|
|
1234
|
+
return "html-fallthrough";
|
|
1235
|
+
// 401/403/200-with-error-body/405/4xx-with-business-error = real route
|
|
1236
|
+
return "verified-real";
|
|
1237
|
+
}
|
|
1238
|
+
function withTimeout(p, ms) {
|
|
1239
|
+
return Promise.race([
|
|
1240
|
+
p,
|
|
1241
|
+
new Promise((resolve) => setTimeout(() => resolve(null), ms)),
|
|
1242
|
+
]);
|
|
1243
|
+
}
|
|
1244
|
+
const results = await Promise.all(scope.map(async (company) => {
|
|
1245
|
+
// lilith uses CDP (puppeteer launches Chrome) — its withTimeout
|
|
1246
|
+
// returns but the browser handle keeps the event loop alive, so
|
|
1247
|
+
// the process never exits. Skip it explicitly; users who want to
|
|
1248
|
+
// recon lilith can scope --companies=lilith and accept the hang.
|
|
1249
|
+
if (company === "lilith") {
|
|
1250
|
+
return { company, classification: "probe-error", detail: "skipped — CDP adapter (puppeteer); pass --companies=lilith explicitly to probe" };
|
|
1251
|
+
}
|
|
1252
|
+
const adapter = ADAPTERS[company];
|
|
1253
|
+
if (!adapter)
|
|
1254
|
+
return { company, classification: "probe-error", detail: "unknown adapter" };
|
|
1255
|
+
if (typeof adapter.fetchApplicationSchema !== "function") {
|
|
1256
|
+
return { company, classification: "probe-error", detail: "no fetchApplicationSchema" };
|
|
1257
|
+
}
|
|
1258
|
+
// Use a placeholder post_id so we don't have to search.
|
|
1259
|
+
// Per-step timeout protects against slow / hung adapters.
|
|
1260
|
+
let schema = null;
|
|
1261
|
+
try {
|
|
1262
|
+
const r = await withTimeout(adapter.fetchApplicationSchema("recon-probe"), 10000);
|
|
1263
|
+
if (r?.ok && r.schema)
|
|
1264
|
+
schema = r.schema;
|
|
1265
|
+
}
|
|
1266
|
+
catch { }
|
|
1267
|
+
if (!schema) {
|
|
1268
|
+
try {
|
|
1269
|
+
const list = await withTimeout(adapter.searchPositions({ pageSize: 1 }), 10000);
|
|
1270
|
+
const pid = list?.positions?.[0]?.post_id;
|
|
1271
|
+
if (pid) {
|
|
1272
|
+
const r = await withTimeout(adapter.fetchApplicationSchema(pid), 10000);
|
|
1273
|
+
if (r?.ok && r.schema)
|
|
1274
|
+
schema = r.schema;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
catch { }
|
|
1278
|
+
}
|
|
1279
|
+
if (!schema)
|
|
1280
|
+
return { company, classification: "probe-error", detail: "schema unavailable" };
|
|
1281
|
+
if (schema.submit_kind === "external") {
|
|
1282
|
+
return { company, submit_kind: schema.submit_kind, classification: "external", detail: "structurally external (Liepin / WeChat)" };
|
|
1283
|
+
}
|
|
1284
|
+
const url = schema.submit_endpoint ?? "";
|
|
1285
|
+
if (!url)
|
|
1286
|
+
return { company, submit_kind: schema.submit_kind, classification: "no-endpoint", detail: "no submit_endpoint in schema" };
|
|
1287
|
+
try {
|
|
1288
|
+
const r = await fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
|
|
1289
|
+
const body = await r.text();
|
|
1290
|
+
const ct = r.headers.get("content-type") ?? "";
|
|
1291
|
+
return {
|
|
1292
|
+
company,
|
|
1293
|
+
submit_kind: schema.submit_kind,
|
|
1294
|
+
submit_endpoint: url,
|
|
1295
|
+
status: r.status,
|
|
1296
|
+
classification: classify(r.status, body, ct),
|
|
1297
|
+
detail: body.slice(0, 80).replace(/\s+/g, " "),
|
|
1298
|
+
already_verified: schema.endpoint_verified === true,
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
catch (err) {
|
|
1302
|
+
return { company, submit_kind: schema.submit_kind, submit_endpoint: url, classification: "probe-error", detail: err instanceof Error ? err.message : String(err) };
|
|
1303
|
+
}
|
|
1304
|
+
}));
|
|
1305
|
+
if (compact) {
|
|
1306
|
+
console.log(JSON.stringify({ probed: results.length, results }));
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
const tally = new Map();
|
|
1310
|
+
for (const r of results)
|
|
1311
|
+
tally.set(r.classification, (tally.get(r.classification) ?? 0) + 1);
|
|
1312
|
+
const width = Math.max(...results.map((r) => r.company.length));
|
|
1313
|
+
const ICON = {
|
|
1314
|
+
"verified-real": "✓",
|
|
1315
|
+
"speculative-404": "✗",
|
|
1316
|
+
"html-fallthrough": "✗",
|
|
1317
|
+
"external": "⛔",
|
|
1318
|
+
"no-endpoint": "·",
|
|
1319
|
+
"probe-error": "?",
|
|
1320
|
+
};
|
|
1321
|
+
console.log(`\njob-pro recon — endpoint probe across ${results.length} adapters`);
|
|
1322
|
+
console.log(` (anon POST with {} body; schema-verified adapters tagged 🟢)\n`);
|
|
1323
|
+
for (const r of results) {
|
|
1324
|
+
const tag = r.status ? `${r.status}` : "—";
|
|
1325
|
+
const vTag = r.already_verified ? " 🟢" : "";
|
|
1326
|
+
console.log(` ${ICON[r.classification]} ${r.company.padEnd(width)} ${tag.padEnd(4)} ${r.classification.padEnd(17)}${vTag} ${r.detail}`);
|
|
1327
|
+
}
|
|
1328
|
+
console.log(`\n Tally:`);
|
|
1329
|
+
for (const [k, v] of [...tally.entries()].sort()) {
|
|
1330
|
+
console.log(` ${k.padEnd(20)} ${v}`);
|
|
1331
|
+
}
|
|
1332
|
+
// Some adapters (cdp/lilith via puppeteer) keep the event loop alive
|
|
1333
|
+
// after their probe resolves. Explicit exit guarantees the CLI returns.
|
|
1334
|
+
process.exit(0);
|
|
1335
|
+
}
|
|
1215
1336
|
if (cmd === "selftest") {
|
|
1216
1337
|
// Three end-to-end checks against the easiest adapter (xpeng, anon-submit):
|
|
1217
1338
|
// 1. searchPositions returns >0 hits
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job-pro",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.43",
|
|
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",
|