@ha7ch/job-pro 1.0.93
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/adapter.js +17 -0
- package/dist/agibot.js +399 -0
- package/dist/alibaba.js +509 -0
- package/dist/antgroup.js +397 -0
- package/dist/apply.js +1373 -0
- package/dist/baichuan.js +49 -0
- package/dist/baidu.js +452 -0
- package/dist/bilibili.js +455 -0
- package/dist/byd.js +412 -0
- package/dist/bytedance.js +619 -0
- package/dist/cainiao.js +56 -0
- package/dist/cambricon.js +33 -0
- package/dist/cdp.js +237 -0
- package/dist/cicc.js +56 -0
- package/dist/coverage.js +60 -0
- package/dist/deepseek.js +25 -0
- package/dist/didi.js +381 -0
- package/dist/feishu.js +577 -0
- package/dist/galaxyuniversal.js +24 -0
- package/dist/geely.js +35 -0
- package/dist/greenhouse.js +432 -0
- package/dist/hikvision.js +58 -0
- package/dist/horizonrobotics.js +46 -0
- package/dist/hoyoverse.js +26 -0
- package/dist/huawei.js +537 -0
- package/dist/iflytek.js +380 -0
- package/dist/index.js +1828 -0
- package/dist/iqiyi.js +494 -0
- package/dist/jd.js +559 -0
- package/dist/kuaishou.js +496 -0
- package/dist/lever.js +455 -0
- package/dist/liauto.js +393 -0
- package/dist/liepin.js +357 -0
- package/dist/lilith.js +300 -0
- package/dist/megvii.js +27 -0
- package/dist/meituan.js +633 -0
- package/dist/memory.js +76 -0
- package/dist/mihoyo.js +308 -0
- package/dist/minimax.js +32 -0
- package/dist/moka.js +473 -0
- package/dist/moonshot.js +24 -0
- package/dist/netease.js +424 -0
- package/dist/nio.js +24 -0
- package/dist/oppo.js +285 -0
- package/dist/pdd.js +614 -0
- package/dist/pingan.js +493 -0
- package/dist/sensetime.js +51 -0
- package/dist/sf.js +310 -0
- package/dist/stepfun.js +24 -0
- package/dist/tencent.js +770 -0
- package/dist/trip.js +396 -0
- package/dist/unitree.js +418 -0
- package/dist/vivo.js +361 -0
- package/dist/webank.js +55 -0
- package/dist/wecruit.js +438 -0
- package/dist/weibo.js +337 -0
- package/dist/weride.js +29 -0
- package/dist/xiaohongshu.js +480 -0
- package/dist/xiaomi.js +529 -0
- package/dist/xpeng.js +34 -0
- package/dist/zerooneai.js +42 -0
- package/dist/zhipu.js +478 -0
- package/extension/README.md +79 -0
- package/extension/background.js +177 -0
- package/extension/manifest.json +55 -0
- package/extension/popup.html +37 -0
- package/extension/popup.js +54 -0
- package/package.json +61 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// job-pro session bridge — background service worker.
|
|
2
|
+
//
|
|
3
|
+
// Captures cookies + recent CSRF / XSRF-Token headers from supported
|
|
4
|
+
// careers sites and stores them in chrome.storage so the popup can hand
|
|
5
|
+
// them off as a downloadable session.json file. The CLI's auto-apply
|
|
6
|
+
// then loads `~/.jobpro/<co>.session.json` and re-uses the captured
|
|
7
|
+
// credentials to fire the actual submission POST.
|
|
8
|
+
//
|
|
9
|
+
// Design constraint: this is a manifest-v3 service worker, so it
|
|
10
|
+
// shouldn't hold state in module-scope (workers can be evicted at any
|
|
11
|
+
// time). All state goes through chrome.storage.local.
|
|
12
|
+
|
|
13
|
+
// Map of careers-site host → adapter key. Used both to identify which
|
|
14
|
+
// "company" a session belongs to and to scope what we export.
|
|
15
|
+
const HOST_TO_KEY = {
|
|
16
|
+
"join.qq.com": "tencent",
|
|
17
|
+
"jobs.bytedance.com": "bytedance",
|
|
18
|
+
"campus-talent.alibaba.com": "alibaba",
|
|
19
|
+
"zhaopin.meituan.com": "meituan",
|
|
20
|
+
"job.xiaohongshu.com": "xiaohongshu",
|
|
21
|
+
"campus.jd.com": "jd",
|
|
22
|
+
"campus.kuaishou.cn": "kuaishou",
|
|
23
|
+
"xiaomi.jobs.f.mioffice.cn": "xiaomi",
|
|
24
|
+
"talent.baidu.com": "baidu",
|
|
25
|
+
"hr.163.com": "netease",
|
|
26
|
+
"talent.didiglobal.com": "didi",
|
|
27
|
+
"jobs.bilibili.com": "bilibili",
|
|
28
|
+
"careers.pinduoduo.com": "pdd",
|
|
29
|
+
"career.huawei.com": "huawei",
|
|
30
|
+
"campus.pingan.com": "pingan",
|
|
31
|
+
"careers.ctrip.com": "trip",
|
|
32
|
+
"www.unitree.com": "unitree",
|
|
33
|
+
"job.byd.com": "byd",
|
|
34
|
+
"talent.antgroup.com": "antgroup",
|
|
35
|
+
"hrcareersweb.antgroup.com": "antgroup",
|
|
36
|
+
"hr.sensetime.com": "sensetime",
|
|
37
|
+
"wecruit.hotjob.cn": "horizonrobotics",
|
|
38
|
+
"app.mokahr.com": "moka",
|
|
39
|
+
"careers.oppo.com": "oppo",
|
|
40
|
+
"hr.vivo.com": "vivo",
|
|
41
|
+
"vivo.zhiye.com": "vivo",
|
|
42
|
+
"iflytek.zhiye.com": "iflytek",
|
|
43
|
+
"campus.sf-express.com": "sf",
|
|
44
|
+
"www.lixiang.com": "liauto",
|
|
45
|
+
"lilithgames.jobs.feishu.cn": "lilith",
|
|
46
|
+
"www.liepin.com": "liepin",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function adapterKeyForHost(host) {
|
|
50
|
+
if (HOST_TO_KEY[host]) return HOST_TO_KEY[host];
|
|
51
|
+
// .jobs.feishu.cn / .zhiye.com wildcard fallback — store by subdomain.
|
|
52
|
+
if (host.endsWith(".jobs.feishu.cn")) return `feishu:${host}`;
|
|
53
|
+
if (host.endsWith(".zhiye.com")) return `zhiye:${host}`;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Cache the latest auth-related request headers per adapter key. We
|
|
58
|
+
// don't keep XHR bodies — only `Cookie` / `X-Xsrf-Token` / `Authorization`
|
|
59
|
+
// / `X-Csrf-Token` / `X-Fscp-Std-Info` etc.
|
|
60
|
+
const AUTH_HEADER_NAMES = new Set([
|
|
61
|
+
"authorization",
|
|
62
|
+
"cookie",
|
|
63
|
+
"x-xsrf-token",
|
|
64
|
+
"x-csrf-token",
|
|
65
|
+
"x-csrftoken",
|
|
66
|
+
"x-requested-with",
|
|
67
|
+
"x-fscp-std-info",
|
|
68
|
+
"x-fscp-version",
|
|
69
|
+
"x-fscp-trace-id",
|
|
70
|
+
"x-client-type",
|
|
71
|
+
"langtype",
|
|
72
|
+
"x-token",
|
|
73
|
+
"x-auth-token",
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
chrome.webRequest?.onSendHeaders?.addListener?.(
|
|
77
|
+
// Note: in MV3 you can't *modify* requests without `declarativeNetRequest`,
|
|
78
|
+
// but reading via webRequest is still allowed for hosts in host_permissions.
|
|
79
|
+
// If the user is on a network where webRequest isn't available we fall
|
|
80
|
+
// back to cookies-only capture (see chrome.cookies below).
|
|
81
|
+
(details) => {
|
|
82
|
+
try {
|
|
83
|
+
const u = new URL(details.url);
|
|
84
|
+
const key = adapterKeyForHost(u.hostname);
|
|
85
|
+
if (!key) return;
|
|
86
|
+
const captured = {};
|
|
87
|
+
for (const h of details.requestHeaders ?? []) {
|
|
88
|
+
const name = (h.name ?? "").toLowerCase();
|
|
89
|
+
if (AUTH_HEADER_NAMES.has(name)) captured[name] = h.value ?? "";
|
|
90
|
+
}
|
|
91
|
+
if (Object.keys(captured).length === 0) return;
|
|
92
|
+
const storageKey = `auth_headers:${key}`;
|
|
93
|
+
chrome.storage.local.get([storageKey]).then((existing) => {
|
|
94
|
+
chrome.storage.local.set({
|
|
95
|
+
[storageKey]: {
|
|
96
|
+
adapter: key,
|
|
97
|
+
host: u.hostname,
|
|
98
|
+
url: details.url,
|
|
99
|
+
captured_at: new Date().toISOString(),
|
|
100
|
+
headers: { ...(existing[storageKey]?.headers ?? {}), ...captured },
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.warn("[job-pro] header capture err:", err);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{ urls: ["<all_urls>"] },
|
|
109
|
+
["requestHeaders", "extraHeaders"]
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Expose a message API for the popup: "give me everything you have for
|
|
113
|
+
// the active tab", and a "clear" command.
|
|
114
|
+
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
115
|
+
(async () => {
|
|
116
|
+
if (msg?.type === "list_sessions") {
|
|
117
|
+
const all = await chrome.storage.local.get(null);
|
|
118
|
+
const out = Object.entries(all)
|
|
119
|
+
.filter(([k]) => k.startsWith("auth_headers:"))
|
|
120
|
+
.map(([k, v]) => ({ key: k.slice("auth_headers:".length), ...v }));
|
|
121
|
+
sendResponse({ ok: true, sessions: out });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (msg?.type === "export_session" && typeof msg.key === "string") {
|
|
125
|
+
const stored = (await chrome.storage.local.get([`auth_headers:${msg.key}`]))[
|
|
126
|
+
`auth_headers:${msg.key}`
|
|
127
|
+
];
|
|
128
|
+
if (!stored) {
|
|
129
|
+
sendResponse({ ok: false, message: `no session captured for ${msg.key}` });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const cookies = await chrome.cookies.getAll({ url: `https://${stored.host}/` });
|
|
133
|
+
const session = {
|
|
134
|
+
adapter: msg.key,
|
|
135
|
+
host: stored.host,
|
|
136
|
+
exported_at: new Date().toISOString(),
|
|
137
|
+
headers: stored.headers,
|
|
138
|
+
cookies: cookies.map((c) => ({
|
|
139
|
+
name: c.name,
|
|
140
|
+
value: c.value,
|
|
141
|
+
domain: c.domain,
|
|
142
|
+
path: c.path,
|
|
143
|
+
expiresAt: c.expirationDate,
|
|
144
|
+
httpOnly: c.httpOnly,
|
|
145
|
+
secure: c.secure,
|
|
146
|
+
sameSite: c.sameSite,
|
|
147
|
+
})),
|
|
148
|
+
};
|
|
149
|
+
const blob = new Blob([JSON.stringify(session, null, 2)], { type: "application/json" });
|
|
150
|
+
const dataUrl = await new Promise((resolve) => {
|
|
151
|
+
const reader = new FileReader();
|
|
152
|
+
reader.onloadend = () => resolve(reader.result);
|
|
153
|
+
reader.readAsDataURL(blob);
|
|
154
|
+
});
|
|
155
|
+
try {
|
|
156
|
+
const dlId = await chrome.downloads.download({
|
|
157
|
+
url: dataUrl,
|
|
158
|
+
filename: `jobpro/${msg.key}.session.json`,
|
|
159
|
+
saveAs: false,
|
|
160
|
+
});
|
|
161
|
+
sendResponse({ ok: true, downloadId: dlId, host: stored.host, cookieCount: cookies.length });
|
|
162
|
+
} catch (err) {
|
|
163
|
+
sendResponse({ ok: false, message: `download failed: ${err?.message ?? String(err)}` });
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (msg?.type === "clear_sessions") {
|
|
168
|
+
const all = await chrome.storage.local.get(null);
|
|
169
|
+
const keys = Object.keys(all).filter((k) => k.startsWith("auth_headers:"));
|
|
170
|
+
await chrome.storage.local.remove(keys);
|
|
171
|
+
sendResponse({ ok: true, cleared: keys.length });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
sendResponse({ ok: false, message: `unknown message type: ${msg?.type}` });
|
|
175
|
+
})().catch((err) => sendResponse({ ok: false, message: String(err) }));
|
|
176
|
+
return true; // async sendResponse
|
|
177
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "job-pro session bridge",
|
|
4
|
+
"short_name": "job-pro",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "Capture careers-site session cookies + CSRF headers for use by the job-pro CLI auto-apply.",
|
|
7
|
+
"permissions": [
|
|
8
|
+
"cookies",
|
|
9
|
+
"storage",
|
|
10
|
+
"activeTab",
|
|
11
|
+
"scripting",
|
|
12
|
+
"downloads"
|
|
13
|
+
],
|
|
14
|
+
"host_permissions": [
|
|
15
|
+
"https://join.qq.com/*",
|
|
16
|
+
"https://jobs.bytedance.com/*",
|
|
17
|
+
"https://campus-talent.alibaba.com/*",
|
|
18
|
+
"https://zhaopin.meituan.com/*",
|
|
19
|
+
"https://job.xiaohongshu.com/*",
|
|
20
|
+
"https://campus.jd.com/*",
|
|
21
|
+
"https://campus.kuaishou.cn/*",
|
|
22
|
+
"https://xiaomi.jobs.f.mioffice.cn/*",
|
|
23
|
+
"https://talent.baidu.com/*",
|
|
24
|
+
"https://hr.163.com/*",
|
|
25
|
+
"https://talent.didiglobal.com/*",
|
|
26
|
+
"https://jobs.bilibili.com/*",
|
|
27
|
+
"https://careers.pinduoduo.com/*",
|
|
28
|
+
"https://career.huawei.com/*",
|
|
29
|
+
"https://campus.pingan.com/*",
|
|
30
|
+
"https://careers.ctrip.com/*",
|
|
31
|
+
"https://www.unitree.com/*",
|
|
32
|
+
"https://job.byd.com/*",
|
|
33
|
+
"https://talent.antgroup.com/*",
|
|
34
|
+
"https://hrcareersweb.antgroup.com/*",
|
|
35
|
+
"https://*.jobs.feishu.cn/*",
|
|
36
|
+
"https://*.zhiye.com/*",
|
|
37
|
+
"https://hr.sensetime.com/*",
|
|
38
|
+
"https://wecruit.hotjob.cn/*",
|
|
39
|
+
"https://app.mokahr.com/*",
|
|
40
|
+
"https://careers.oppo.com/*",
|
|
41
|
+
"https://hr.vivo.com/*",
|
|
42
|
+
"https://campus.sf-express.com/*",
|
|
43
|
+
"https://www.lixiang.com/*",
|
|
44
|
+
"https://lilithgames.jobs.feishu.cn/*",
|
|
45
|
+
"https://www.liepin.com/*"
|
|
46
|
+
],
|
|
47
|
+
"background": {
|
|
48
|
+
"service_worker": "background.js",
|
|
49
|
+
"type": "module"
|
|
50
|
+
},
|
|
51
|
+
"action": {
|
|
52
|
+
"default_popup": "popup.html",
|
|
53
|
+
"default_title": "job-pro session bridge"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>job-pro session bridge</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font: 13px/1.4 -apple-system, system-ui, sans-serif; margin: 12px; min-width: 320px; }
|
|
8
|
+
h1 { font-size: 14px; margin: 0 0 8px; }
|
|
9
|
+
p.lede { color: #555; margin: 0 0 12px; }
|
|
10
|
+
ul { margin: 0; padding: 0; list-style: none; }
|
|
11
|
+
li { display: flex; align-items: center; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid #eee; }
|
|
12
|
+
li:last-child { border-bottom: 0; }
|
|
13
|
+
.key { font-weight: 600; }
|
|
14
|
+
.meta { color: #888; font-size: 11px; }
|
|
15
|
+
button { font: inherit; padding: 4px 8px; border: 1px solid #ccc; background: #f5f5f5; border-radius: 3px; cursor: pointer; }
|
|
16
|
+
button.danger { color: #c00; }
|
|
17
|
+
#status { margin-top: 12px; padding: 8px; background: #f5f9f0; border-left: 3px solid #5a5; font-size: 11px; white-space: pre-wrap; }
|
|
18
|
+
#status.error { background: #f9eef0; border-color: #c55; }
|
|
19
|
+
#empty { color: #888; padding: 16px 0; text-align: center; }
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<h1>job-pro session bridge</h1>
|
|
24
|
+
<p class="lede">
|
|
25
|
+
Captured careers-site sessions. Click <strong>Export</strong> on a row
|
|
26
|
+
to download <code><adapter>.session.json</code>, then move it
|
|
27
|
+
into <code>~/.jobpro/</code> for the CLI's <code>apply</code> verb.
|
|
28
|
+
</p>
|
|
29
|
+
<ul id="sessions"></ul>
|
|
30
|
+
<div id="empty" hidden>No sessions captured yet. Browse a supported careers site (see <code>manifest.json</code>) while logged in, then re-open this popup.</div>
|
|
31
|
+
<div id="status" hidden></div>
|
|
32
|
+
<p style="margin-top:14px;text-align:right">
|
|
33
|
+
<button id="clear" class="danger">Clear all</button>
|
|
34
|
+
</p>
|
|
35
|
+
<script src="popup.js"></script>
|
|
36
|
+
</body>
|
|
37
|
+
</html>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const sessionsEl = document.getElementById("sessions");
|
|
2
|
+
const emptyEl = document.getElementById("empty");
|
|
3
|
+
const statusEl = document.getElementById("status");
|
|
4
|
+
const clearBtn = document.getElementById("clear");
|
|
5
|
+
|
|
6
|
+
function showStatus(text, isError) {
|
|
7
|
+
statusEl.textContent = text;
|
|
8
|
+
statusEl.hidden = false;
|
|
9
|
+
statusEl.classList.toggle("error", !!isError);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function send(msg) {
|
|
13
|
+
return new Promise((resolve) => chrome.runtime.sendMessage(msg, resolve));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function render() {
|
|
17
|
+
const r = await send({ type: "list_sessions" });
|
|
18
|
+
if (!r?.ok || !r.sessions?.length) {
|
|
19
|
+
sessionsEl.innerHTML = "";
|
|
20
|
+
emptyEl.hidden = false;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
emptyEl.hidden = true;
|
|
24
|
+
sessionsEl.innerHTML = "";
|
|
25
|
+
for (const s of r.sessions) {
|
|
26
|
+
const li = document.createElement("li");
|
|
27
|
+
const left = document.createElement("div");
|
|
28
|
+
const right = document.createElement("button");
|
|
29
|
+
left.innerHTML = `<span class="key">${s.key}</span> <span class="meta">${s.host} · ${s.captured_at?.slice(0, 19) ?? "?"}</span>`;
|
|
30
|
+
right.textContent = "Export";
|
|
31
|
+
right.addEventListener("click", async () => {
|
|
32
|
+
const exp = await send({ type: "export_session", key: s.key });
|
|
33
|
+
if (exp?.ok) {
|
|
34
|
+
showStatus(
|
|
35
|
+
`Saved jobpro/${s.key}.session.json — ${exp.cookieCount} cookies + headers from ${exp.host}.\n` +
|
|
36
|
+
`Move it into ~/.jobpro/${s.key}.session.json for the CLI.`
|
|
37
|
+
);
|
|
38
|
+
} else {
|
|
39
|
+
showStatus(`Export failed: ${exp?.message ?? "unknown error"}`, true);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
li.appendChild(left);
|
|
43
|
+
li.appendChild(right);
|
|
44
|
+
sessionsEl.appendChild(li);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
clearBtn.addEventListener("click", async () => {
|
|
49
|
+
const r = await send({ type: "clear_sessions" });
|
|
50
|
+
showStatus(r?.ok ? `Cleared ${r.cleared} session(s).` : `Failed: ${r?.message}`, !r?.ok);
|
|
51
|
+
render();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
render();
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ha7ch/job-pro",
|
|
3
|
+
"version": "1.0.93",
|
|
4
|
+
"description": "Query Chinese big-tech campus recruiting + auto-apply from your terminal. 50 companies, all 50 live (46 via official APIs, 4 via Liepin third-party fallback). 45/50 with end-to-end verified apply endpoints; 5 structurally-external (Liepin IM × 4 + Unitree WeChat). No signup, no token, no server.",
|
|
5
|
+
"homepage": "https://job.ha7ch.com",
|
|
6
|
+
"repository": "https://github.com/HA7CH/job-pro",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"job-pro": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"extension"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.org/"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"campus-recruiting",
|
|
22
|
+
"校招",
|
|
23
|
+
"auto-apply",
|
|
24
|
+
"投递",
|
|
25
|
+
"tencent",
|
|
26
|
+
"bytedance",
|
|
27
|
+
"alibaba",
|
|
28
|
+
"meituan",
|
|
29
|
+
"xiaohongshu",
|
|
30
|
+
"job-search",
|
|
31
|
+
"ats",
|
|
32
|
+
"greenhouse",
|
|
33
|
+
"lever",
|
|
34
|
+
"feishu",
|
|
35
|
+
"moka",
|
|
36
|
+
"beisen",
|
|
37
|
+
"liepin",
|
|
38
|
+
"cli",
|
|
39
|
+
"claude-code"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc",
|
|
43
|
+
"dev": "tsx src/index.ts",
|
|
44
|
+
"test": "tsx test/smoke.ts",
|
|
45
|
+
"test:apply": "tsx test/apply-smoke.ts",
|
|
46
|
+
"test:debug-submit": "tsx test/debug-submit-smoke.ts",
|
|
47
|
+
"test:unit": "tsx test/unit-smoke.ts",
|
|
48
|
+
"prepublishOnly": "npm run build && rm -rf extension && cp -R ../extension extension"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"puppeteer-core": "^25.0.2"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^20",
|
|
55
|
+
"tsx": "^4",
|
|
56
|
+
"typescript": "^5"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18"
|
|
60
|
+
}
|
|
61
|
+
}
|