job-pro 1.0.40 → 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.
Files changed (2) hide show
  1. package/dist/index.js +108 -0
  2. 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.40",
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",