job-pro 1.0.2 → 1.0.4

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 +193 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -53,9 +53,14 @@ import * as cambricon from "./cambricon.js";
53
53
  import { loadProfile, loadSession, profileTemplate, 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 } from "node:fs";
57
- import { dirname } from "node:path";
58
- const VERSION = "1.0.2";
56
+ import { writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from "node:fs";
57
+ import { dirname, join } from "node:path";
58
+ import { homedir } from "node:os";
59
+ import { createRequire as require_createRequire } from "node:module";
60
+ function require_module() {
61
+ return { createRequire: require_createRequire };
62
+ }
63
+ const VERSION = "1.0.4";
59
64
  const COMPANIES = [
60
65
  { key: "tencent", family: "Bespoke", source: "join.qq.com", label: "Tencent / 腾讯" },
61
66
  { key: "bytedance", family: "Bespoke", source: "jobs.bytedance.com", label: "ByteDance / 字节跳动" },
@@ -115,6 +120,7 @@ job-pro — query Chinese big-tech campus recruiting from your terminal
115
120
  USAGE
116
121
  job-pro <company> <verb> [options]
117
122
  job-pro list [--compact] list all 50 companies + source family
123
+ job-pro status [--compact] survey profile / sessions / memory / chrome
118
124
  job-pro profile init [--force] write ~/.jobpro/profile.json template
119
125
  job-pro profile show print the loaded profile
120
126
  job-pro --version
@@ -671,6 +677,185 @@ async function runCompany(adapter, company, rawArgs) {
671
677
  }
672
678
  die(`unknown verb: ${verb}. Try \`job-pro help\`.`);
673
679
  }
680
+ function buildStatusReport() {
681
+ const homeDir = process.env.JOBPRO_HOME ?? join(homedir(), ".jobpro");
682
+ const profilePath = process.env.JOB_PRO_PROFILE_PATH ?? join(homeDir, "profile.json");
683
+ const sessionDir = process.env.JOB_PRO_SESSION_DIR ?? homeDir;
684
+ // Profile state.
685
+ const filled = [];
686
+ const missing = [];
687
+ let customKeys = 0;
688
+ let profileExists = false;
689
+ if (existsSync(profilePath)) {
690
+ profileExists = true;
691
+ try {
692
+ const p = JSON.parse(readFileSync(profilePath, "utf8"));
693
+ for (const key of ["first_name", "last_name", "email", "phone", "resume_path"]) {
694
+ const v = p[key];
695
+ if (typeof v === "string" && v.length > 0)
696
+ filled.push(key);
697
+ else
698
+ missing.push(key);
699
+ }
700
+ customKeys = p.custom && typeof p.custom === "object" ? Object.keys(p.custom).length : 0;
701
+ }
702
+ catch {
703
+ missing.push("(profile JSON is malformed)");
704
+ }
705
+ }
706
+ else {
707
+ missing.push("first_name", "last_name", "email", "phone", "resume_path");
708
+ }
709
+ // Captured sessions in ~/.jobpro/*.session.json
710
+ const sessions = [];
711
+ if (existsSync(sessionDir)) {
712
+ try {
713
+ for (const f of readdirSync(sessionDir)) {
714
+ if (!f.endsWith(".session.json"))
715
+ continue;
716
+ const adapter = f.slice(0, -".session.json".length);
717
+ const full = join(sessionDir, f);
718
+ const stat = statSync(full);
719
+ const age = (Date.now() - stat.mtimeMs) / (24 * 3600 * 1000);
720
+ let host;
721
+ let cookieCount = 0;
722
+ let headerCount = 0;
723
+ let capturedAt;
724
+ try {
725
+ const j = JSON.parse(readFileSync(full, "utf8"));
726
+ host = j.host;
727
+ cookieCount = Array.isArray(j.cookies) ? j.cookies.length : 0;
728
+ headerCount = j.headers ? Object.keys(j.headers).length : 0;
729
+ capturedAt = j.exported_at;
730
+ }
731
+ catch {
732
+ /* malformed — still surface the file */
733
+ }
734
+ sessions.push({
735
+ adapter,
736
+ path: full,
737
+ host,
738
+ captured_at: capturedAt,
739
+ age_days: Math.round(age * 10) / 10,
740
+ cookies: cookieCount,
741
+ headers: headerCount,
742
+ });
743
+ }
744
+ }
745
+ catch {
746
+ /* ignore */
747
+ }
748
+ }
749
+ // Memory snapshot.
750
+ const memSummary = {
751
+ field_keys: [],
752
+ recent_events: [],
753
+ total_events: 0,
754
+ };
755
+ try {
756
+ const memList = memoryList();
757
+ if (memList?.path)
758
+ memSummary.path = memList.path;
759
+ if (memList?.fields)
760
+ memSummary.field_keys = Object.keys(memList.fields);
761
+ if (Array.isArray(memList?.events)) {
762
+ memSummary.total_events = memList.events.length;
763
+ memSummary.recent_events = memList.events.slice(-5).reverse();
764
+ }
765
+ }
766
+ catch {
767
+ /* ignore */
768
+ }
769
+ // Chrome / puppeteer-core availability.
770
+ const CHROME_CANDIDATES = [
771
+ process.env.JOB_PRO_CHROME,
772
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
773
+ "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
774
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
775
+ "/usr/bin/google-chrome",
776
+ "/usr/bin/google-chrome-stable",
777
+ "/usr/bin/chromium",
778
+ "/usr/bin/chromium-browser",
779
+ ].filter((p) => typeof p === "string" && p.length > 0);
780
+ const chromePath = CHROME_CANDIDATES.find((p) => existsSync(p));
781
+ // puppeteer-core is a runtime dep, but a user could have done --omit=optional
782
+ // or be running from a fresh checkout. Probe via createRequire because
783
+ // we're an ESM module without a CJS `require`.
784
+ let hasPuppeteer = false;
785
+ try {
786
+ const { createRequire } = require_module();
787
+ const req = createRequire(import.meta.url);
788
+ req.resolve("puppeteer-core");
789
+ hasPuppeteer = true;
790
+ }
791
+ catch {
792
+ hasPuppeteer = false;
793
+ }
794
+ return {
795
+ profile: {
796
+ path: profilePath,
797
+ exists: profileExists,
798
+ filled_standard: filled,
799
+ missing_standard: missing,
800
+ custom_keys: customKeys,
801
+ },
802
+ sessions,
803
+ memory: memSummary,
804
+ chrome: { found: !!chromePath, path: chromePath, puppeteer_core: hasPuppeteer },
805
+ };
806
+ }
807
+ function printStatus(compact) {
808
+ const r = buildStatusReport();
809
+ if (compact) {
810
+ console.log(JSON.stringify(r));
811
+ return;
812
+ }
813
+ console.log(`job-pro status (${VERSION})`);
814
+ console.log();
815
+ // Profile
816
+ const filledColor = (r.profile.missing_standard.length === 0 && r.profile.exists) ? "✓" : "✗";
817
+ console.log(`Profile ${filledColor} ${r.profile.path}`);
818
+ if (!r.profile.exists) {
819
+ console.log(` not found — run \`job-pro profile init\``);
820
+ }
821
+ else {
822
+ console.log(` filled: ${r.profile.filled_standard.join(", ") || "(none)"}`);
823
+ if (r.profile.missing_standard.length > 0) {
824
+ console.log(` missing: ${r.profile.missing_standard.join(", ")}`);
825
+ }
826
+ if (r.profile.custom_keys > 0) {
827
+ console.log(` custom: ${r.profile.custom_keys} keys`);
828
+ }
829
+ }
830
+ console.log();
831
+ // Sessions
832
+ if (r.sessions.length === 0) {
833
+ console.log(`Sessions ✗ no session.json files captured`);
834
+ console.log(` install extension/ in Chrome to capture sessions for non-anon adapters.`);
835
+ }
836
+ else {
837
+ console.log(`Sessions ✓ ${r.sessions.length} captured`);
838
+ for (const s of r.sessions) {
839
+ const stale = (s.age_days ?? 0) > 30 ? " (STALE — sessions usually expire ~30 days)" : "";
840
+ console.log(` ${s.adapter.padEnd(18)} ${s.cookies ?? 0}c+${s.headers ?? 0}h age=${s.age_days}d${stale}`);
841
+ }
842
+ }
843
+ console.log();
844
+ // Memory
845
+ console.log(`Memory ${r.memory.total_events > 0 ? "✓" : "·"} ${r.memory.path ?? "(none)"}`);
846
+ console.log(` fields=${r.memory.field_keys.length} events=${r.memory.total_events}`);
847
+ for (const e of r.memory.recent_events.slice(0, 5)) {
848
+ console.log(` ${e.ts} ${e.kind.padEnd(12)} ${(e.payload ?? "").slice(0, 60)}`);
849
+ }
850
+ console.log();
851
+ // Chrome
852
+ const ch = r.chrome.found && r.chrome.puppeteer_core ? "✓" : "✗";
853
+ console.log(`Chrome ${ch} ${r.chrome.path ?? "(not found)"}`);
854
+ console.log(` puppeteer-core: ${r.chrome.puppeteer_core ? "installed" : "missing"}`);
855
+ if (!r.chrome.found || !r.chrome.puppeteer_core) {
856
+ console.log(` needed for: lilith adapter, --proxy-server geo-bypass (hikvision).`);
857
+ }
858
+ }
674
859
  function printCompanyList(compact) {
675
860
  // Validate the directory still matches the ADAPTERS map. If a company
676
861
  // appears in only one place, treat it as a bug.
@@ -734,6 +919,11 @@ async function main() {
734
919
  printCompanyList(compact);
735
920
  return;
736
921
  }
922
+ if (cmd === "status") {
923
+ const compact = args.includes("--compact");
924
+ printStatus(compact);
925
+ return;
926
+ }
737
927
  if (cmd === "profile") {
738
928
  const sub = args[1];
739
929
  if (sub === "init") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "job-pro",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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",