job-pro 1.0.41 → 1.0.42
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 +108 -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,111 @@ 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
|
+
const results = await Promise.all(scope.map(async (company) => {
|
|
1239
|
+
const adapter = ADAPTERS[company];
|
|
1240
|
+
if (!adapter)
|
|
1241
|
+
return { company, classification: "probe-error", detail: "unknown adapter" };
|
|
1242
|
+
if (typeof adapter.fetchApplicationSchema !== "function") {
|
|
1243
|
+
return { company, classification: "probe-error", detail: "no fetchApplicationSchema" };
|
|
1244
|
+
}
|
|
1245
|
+
// Use a placeholder post_id so we don't have to search.
|
|
1246
|
+
let schema = null;
|
|
1247
|
+
try {
|
|
1248
|
+
const r = (await adapter.fetchApplicationSchema("recon-probe"));
|
|
1249
|
+
if (r.ok && r.schema)
|
|
1250
|
+
schema = r.schema;
|
|
1251
|
+
}
|
|
1252
|
+
catch { }
|
|
1253
|
+
// Most bespoke adapters return a valid schema even on bogus id,
|
|
1254
|
+
// because they build it from the id alone. The ones that need a
|
|
1255
|
+
// real id (mihoyo etc.) fall through to "fetch a real id".
|
|
1256
|
+
if (!schema) {
|
|
1257
|
+
try {
|
|
1258
|
+
const list = (await adapter.searchPositions({ pageSize: 1 }));
|
|
1259
|
+
const pid = list.positions?.[0]?.post_id;
|
|
1260
|
+
if (pid) {
|
|
1261
|
+
const r = (await adapter.fetchApplicationSchema(pid));
|
|
1262
|
+
if (r.ok && r.schema)
|
|
1263
|
+
schema = r.schema;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
catch { }
|
|
1267
|
+
}
|
|
1268
|
+
if (!schema)
|
|
1269
|
+
return { company, classification: "probe-error", detail: "schema unavailable" };
|
|
1270
|
+
if (schema.submit_kind === "external") {
|
|
1271
|
+
return { company, submit_kind: schema.submit_kind, classification: "external", detail: "structurally external (Liepin / WeChat)" };
|
|
1272
|
+
}
|
|
1273
|
+
const url = schema.submit_endpoint ?? "";
|
|
1274
|
+
if (!url)
|
|
1275
|
+
return { company, submit_kind: schema.submit_kind, classification: "no-endpoint", detail: "no submit_endpoint in schema" };
|
|
1276
|
+
try {
|
|
1277
|
+
const r = await fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
|
|
1278
|
+
const body = await r.text();
|
|
1279
|
+
const ct = r.headers.get("content-type") ?? "";
|
|
1280
|
+
return {
|
|
1281
|
+
company,
|
|
1282
|
+
submit_kind: schema.submit_kind,
|
|
1283
|
+
submit_endpoint: url,
|
|
1284
|
+
status: r.status,
|
|
1285
|
+
classification: classify(r.status, body, ct),
|
|
1286
|
+
detail: body.slice(0, 80).replace(/\s+/g, " "),
|
|
1287
|
+
already_verified: schema.endpoint_verified === true,
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
catch (err) {
|
|
1291
|
+
return { company, submit_kind: schema.submit_kind, submit_endpoint: url, classification: "probe-error", detail: err instanceof Error ? err.message : String(err) };
|
|
1292
|
+
}
|
|
1293
|
+
}));
|
|
1294
|
+
if (compact) {
|
|
1295
|
+
console.log(JSON.stringify({ probed: results.length, results }));
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
const tally = new Map();
|
|
1299
|
+
for (const r of results)
|
|
1300
|
+
tally.set(r.classification, (tally.get(r.classification) ?? 0) + 1);
|
|
1301
|
+
const width = Math.max(...results.map((r) => r.company.length));
|
|
1302
|
+
const ICON = {
|
|
1303
|
+
"verified-real": "✓",
|
|
1304
|
+
"speculative-404": "✗",
|
|
1305
|
+
"html-fallthrough": "✗",
|
|
1306
|
+
"external": "⛔",
|
|
1307
|
+
"no-endpoint": "·",
|
|
1308
|
+
"probe-error": "?",
|
|
1309
|
+
};
|
|
1310
|
+
console.log(`\njob-pro recon — endpoint probe across ${results.length} adapters`);
|
|
1311
|
+
console.log(` (anon POST with {} body; schema-verified adapters tagged 🟢)\n`);
|
|
1312
|
+
for (const r of results) {
|
|
1313
|
+
const tag = r.status ? `${r.status}` : "—";
|
|
1314
|
+
const vTag = r.already_verified ? " 🟢" : "";
|
|
1315
|
+
console.log(` ${ICON[r.classification]} ${r.company.padEnd(width)} ${tag.padEnd(4)} ${r.classification.padEnd(17)}${vTag} ${r.detail}`);
|
|
1316
|
+
}
|
|
1317
|
+
console.log(`\n Tally:`);
|
|
1318
|
+
for (const [k, v] of [...tally.entries()].sort()) {
|
|
1319
|
+
console.log(` ${k.padEnd(20)} ${v}`);
|
|
1320
|
+
}
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1215
1323
|
if (cmd === "selftest") {
|
|
1216
1324
|
// Three end-to-end checks against the easiest adapter (xpeng, anon-submit):
|
|
1217
1325
|
// 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.42",
|
|
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",
|