job-pro 1.0.28 → 1.0.30

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 +116 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -53,10 +53,10 @@ import * as cambricon from "./cambricon.js";
53
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
- import { writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from "node:fs";
56
+ import { writeFileSync, mkdirSync, existsSync, readdirSync, statSync, mkdtempSync, rmSync } from "node:fs";
57
57
  import { dirname, join } from "node:path";
58
58
  import { fileURLToPath } from "node:url";
59
- import { homedir } from "node:os";
59
+ import { homedir, tmpdir } from "node:os";
60
60
  import { createRequire as require_createRequire } from "node:module";
61
61
  function require_module() {
62
62
  return { createRequire: require_createRequire };
@@ -142,6 +142,7 @@ USAGE
142
142
  job-pro <company> <verb> [options]
143
143
  job-pro list [--compact] list all 50 companies + source family
144
144
  job-pro status [--compact] survey profile / sessions / memory / chrome
145
+ job-pro selftest [--compact] end-to-end check: search → schema → echo-submit
145
146
  job-pro profile init [--interactive] [--force]
146
147
  write ~/.jobpro/profile.json
147
148
  --interactive fills it via prompts.
@@ -197,6 +198,7 @@ VERBS (same surface for every company)
197
198
  --remember + persist answers to profile.custom
198
199
  --batch <file|-> apply to many post_ids (one/line)
199
200
  --debug-submit-to <url> verify wire format
201
+ --debug-submit ↑ shorthand → httpbin.org/post
200
202
  --really-submit actually fire (env-gated)
201
203
  --allow-stale-session bypass 30-day session-age gate
202
204
  memory list | get <k> | set k=v | event <kind> [payload] | clear
@@ -451,7 +453,12 @@ async function runCompany(adapter, company, rawArgs) {
451
453
  const schemaOnly = args.includes("--schema");
452
454
  const interactive = args.includes("--interactive");
453
455
  const remember = args.includes("--remember");
454
- const { args: aDebug, value: debugUrl } = popFlagValue(args, "--debug-submit-to");
456
+ let { args: aDebug, value: debugUrl } = popFlagValue(args, "--debug-submit-to");
457
+ // Shorthand: `--debug-submit` without URL → default httpbin echo.
458
+ if (!debugUrl && aDebug.includes("--debug-submit")) {
459
+ aDebug = aDebug.filter((a) => a !== "--debug-submit");
460
+ debugUrl = "https://httpbin.org/post";
461
+ }
455
462
  const { args: aForm, value: formFilePath } = popFlagValue(aDebug, "--form-file");
456
463
  const { args: aBatch, value: batchPath } = popFlagValue(aForm, "--batch");
457
464
  // Batch mode: read post_ids from a file (or stdin if "-"). Each non-empty,
@@ -1170,6 +1177,112 @@ async function main() {
1170
1177
  printStatus(compact);
1171
1178
  return;
1172
1179
  }
1180
+ if (cmd === "selftest") {
1181
+ // Three end-to-end checks against the easiest adapter (xpeng, anon-submit):
1182
+ // 1. searchPositions returns >0 hits
1183
+ // 2. fetchApplicationSchema for the first hit returns ok:true with questions
1184
+ // 3. submitApplication(staged, {kind:"debug", url:httpbin}) returns 200
1185
+ // Total ~3-5s. No profile / no session needed. Useful right after install
1186
+ // to confirm the CLI can actually round-trip end-to-end.
1187
+ const compact = args.includes("--compact");
1188
+ const xpengAdapter = ADAPTERS.xpeng;
1189
+ const checks = [];
1190
+ async function run(name, fn) {
1191
+ const t0 = Date.now();
1192
+ try {
1193
+ const r = await fn();
1194
+ checks.push({ name, ok: true, detail: "", ms: Date.now() - t0 });
1195
+ return r;
1196
+ }
1197
+ catch (err) {
1198
+ checks.push({ name, ok: false, detail: err instanceof Error ? err.message : String(err), ms: Date.now() - t0 });
1199
+ return null;
1200
+ }
1201
+ }
1202
+ // Step 1
1203
+ const list = await run("search xpeng", async () => {
1204
+ const r = (await xpengAdapter.searchPositions({ pageSize: 1 }));
1205
+ if (!r.ok || !r.positions?.[0]?.post_id)
1206
+ throw new Error("no positions returned");
1207
+ return r;
1208
+ });
1209
+ let postId = null;
1210
+ let title = "";
1211
+ if (list && list.positions?.[0]) {
1212
+ postId = String(list.positions[0].post_id ?? "");
1213
+ title = String(list.positions[0].title ?? "").trim();
1214
+ }
1215
+ // Step 2
1216
+ let schema = null;
1217
+ if (postId && typeof xpengAdapter.fetchApplicationSchema === "function") {
1218
+ schema = await run("fetch schema", async () => {
1219
+ const r = (await xpengAdapter.fetchApplicationSchema(postId));
1220
+ if (!r.ok || !r.schema)
1221
+ throw new Error(r.message ?? "schema fetch failed");
1222
+ return r.schema;
1223
+ });
1224
+ }
1225
+ else if (!postId) {
1226
+ checks.push({ name: "fetch schema", ok: false, detail: "skipped — no post_id from search", ms: 0 });
1227
+ }
1228
+ // Step 3
1229
+ if (schema) {
1230
+ const tmp = mkdtempSync(join(tmpdir(), "jobpro-selftest-"));
1231
+ const resumePath = join(tmp, "resume.pdf");
1232
+ writeFileSync(resumePath, "%PDF\n");
1233
+ const profile = {
1234
+ first_name: "Self", last_name: "Test", email: "selftest@example.com",
1235
+ phone: "+86 13800138000", resume_path: resumePath, cover_letter_text: "",
1236
+ custom: {},
1237
+ };
1238
+ // Auto-fill required: first allowed value for selects, "N/A" for text.
1239
+ for (const q of schema.questions) {
1240
+ if (!q.required)
1241
+ continue;
1242
+ const f = q.fields[0];
1243
+ if (!f)
1244
+ continue;
1245
+ if (["input_text", "textarea"].includes(f.type))
1246
+ profile.custom[f.name] = "N/A (selftest)";
1247
+ else if (f.type.includes("select")) {
1248
+ const first = f.values?.[0];
1249
+ if (first && typeof first.value !== "undefined")
1250
+ profile.custom[f.name] = String(first.value);
1251
+ }
1252
+ }
1253
+ const staged = stageApplication(schema, profile);
1254
+ if (!staged.ready) {
1255
+ checks.push({ name: "debug-submit echo", ok: false, detail: `staged not ready: ${staged.unanswered_required.slice(0, 3).join(", ")}`, ms: 0 });
1256
+ }
1257
+ else {
1258
+ await run("debug-submit echo", async () => {
1259
+ const r = (await submitApplication(staged, { kind: "debug", url: "https://httpbin.org/post" }));
1260
+ if (r.ok !== true || r.status !== 200)
1261
+ throw new Error(`echo failed: ok=${r.ok} status=${r.status} msg=${r.message}`);
1262
+ return r;
1263
+ });
1264
+ }
1265
+ rmSync(tmp, { recursive: true, force: true });
1266
+ }
1267
+ const fails = checks.filter((c) => !c.ok).length;
1268
+ if (compact) {
1269
+ console.log(JSON.stringify({ ok: fails === 0, checks }));
1270
+ }
1271
+ else {
1272
+ console.log(`\njob-pro selftest — using xpeng (anon Greenhouse board)\n`);
1273
+ for (const c of checks) {
1274
+ const icon = c.ok ? "✓" : "✗";
1275
+ const detail = c.detail ? ` ${c.detail}` : "";
1276
+ console.log(` ${icon} ${c.name.padEnd(20)} ${c.ms}ms${detail}`);
1277
+ }
1278
+ console.log(`\n ${checks.length - fails} pass / ${fails} fail / ${checks.length} total${title ? ` — sampled "${title}"` : ""}`);
1279
+ if (fails === 0)
1280
+ console.log(`\n Setup looks good. Run \`job-pro find "<keyword>"\` to scan all 50 companies.`);
1281
+ }
1282
+ if (fails > 0)
1283
+ process.exit(1);
1284
+ return;
1285
+ }
1173
1286
  if (cmd === "extension") {
1174
1287
  // Locate the extension/ directory. The package ships it as a sibling of
1175
1288
  // dist/, so __dirname is cli/dist and the extension lives at ../extension.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "job-pro",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
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",