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.
- package/dist/index.js +193 -3
- 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
|
-
|
|
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.
|
|
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",
|