@alfe.ai/openclaw-linkedin 0.1.0
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/README.md +126 -0
- package/THIRD_PARTY_NOTICES.md +24 -0
- package/dist/cli.cjs +45 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +46 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/installer.cjs +320 -0
- package/dist/installer.js +285 -0
- package/dist/plugin.cjs +5 -0
- package/dist/plugin.d.cts +6 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +6 -0
- package/dist/runtime.cjs +525 -0
- package/dist/runtime.d.cts +117 -0
- package/dist/runtime.d.ts +117 -0
- package/dist/runtime.js +496 -0
- package/openclaw.plugin.json +8 -0
- package/package.json +62 -0
- package/python/requirements.txt +3 -0
- package/python/supervisor.py +149 -0
- package/python/worker.py +235 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { access, chmod, lstat, mkdir, mkdtemp, readdir, rename, rm } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
//#region src/paths.ts
|
|
8
|
+
const EXTRACTOR_VERSION = "4.24.0";
|
|
9
|
+
const PATCHRIGHT_VERSION = "1.61.2";
|
|
10
|
+
const DOTENV_VERSION = "1.2.3";
|
|
11
|
+
const RUNTIME_ID = `runtime-${EXTRACTOR_VERSION}-patchright-${PATCHRIGHT_VERSION}-dotenv-${DOTENV_VERSION}`;
|
|
12
|
+
function stateDirectory() {
|
|
13
|
+
return join(homedir(), ".alfe", "linkedin");
|
|
14
|
+
}
|
|
15
|
+
function runtimeDirectory() {
|
|
16
|
+
return join(stateDirectory(), RUNTIME_ID);
|
|
17
|
+
}
|
|
18
|
+
function runtimePython(directory = runtimeDirectory()) {
|
|
19
|
+
return join(directory, process.platform === "win32" ? "Scripts/python.exe" : "bin/python");
|
|
20
|
+
}
|
|
21
|
+
function workerScript() {
|
|
22
|
+
return fileURLToPath(new URL("../python/worker.py", import.meta.url));
|
|
23
|
+
}
|
|
24
|
+
/** No Alfe/provider tokens, interpreter injection, proxy credentials or traces. */
|
|
25
|
+
function workerEnvironment(source = process.env) {
|
|
26
|
+
const env = {};
|
|
27
|
+
for (const key of [
|
|
28
|
+
"PATH",
|
|
29
|
+
"SystemRoot",
|
|
30
|
+
"WINDIR",
|
|
31
|
+
"TEMP",
|
|
32
|
+
"TMP",
|
|
33
|
+
"TMPDIR",
|
|
34
|
+
"HOME",
|
|
35
|
+
"USERPROFILE",
|
|
36
|
+
"LANG",
|
|
37
|
+
"LC_ALL"
|
|
38
|
+
]) if (source[key]) env[key] = source[key];
|
|
39
|
+
env.LINKEDIN_TRACE_MODE = "off";
|
|
40
|
+
env.LINKEDIN_DEBUG_TRACE_DIR = "";
|
|
41
|
+
env.PYTHON_DOTENV_DISABLED = "1";
|
|
42
|
+
env.PYTHONUNBUFFERED = "1";
|
|
43
|
+
env.PYTHONDONTWRITEBYTECODE = "1";
|
|
44
|
+
return env;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/installer-env.ts
|
|
48
|
+
const INSTALLER_NETWORK_KEYS = [
|
|
49
|
+
"HTTP_PROXY",
|
|
50
|
+
"HTTPS_PROXY",
|
|
51
|
+
"ALL_PROXY",
|
|
52
|
+
"NO_PROXY",
|
|
53
|
+
"http_proxy",
|
|
54
|
+
"https_proxy",
|
|
55
|
+
"all_proxy",
|
|
56
|
+
"no_proxy",
|
|
57
|
+
"PIP_INDEX_URL",
|
|
58
|
+
"PIP_EXTRA_INDEX_URL",
|
|
59
|
+
"PIP_NO_INDEX",
|
|
60
|
+
"PIP_FIND_LINKS",
|
|
61
|
+
"PIP_TRUSTED_HOST",
|
|
62
|
+
"PIP_CERT",
|
|
63
|
+
"PIP_CLIENT_CERT",
|
|
64
|
+
"PIP_CONFIG_FILE",
|
|
65
|
+
"PIP_PROXY",
|
|
66
|
+
"PIP_KEYRING_PROVIDER",
|
|
67
|
+
"PIP_TIMEOUT",
|
|
68
|
+
"PIP_RETRIES",
|
|
69
|
+
"UV_INDEX",
|
|
70
|
+
"UV_DEFAULT_INDEX",
|
|
71
|
+
"UV_INDEX_URL",
|
|
72
|
+
"UV_EXTRA_INDEX_URL",
|
|
73
|
+
"UV_NO_INDEX",
|
|
74
|
+
"UV_FIND_LINKS",
|
|
75
|
+
"UV_INDEX_STRATEGY",
|
|
76
|
+
"UV_INSECURE_HOST",
|
|
77
|
+
"UV_NATIVE_TLS",
|
|
78
|
+
"UV_SYSTEM_CERTS",
|
|
79
|
+
"UV_CONFIG_FILE",
|
|
80
|
+
"UV_CREDENTIALS_DIR",
|
|
81
|
+
"UV_KEYRING_PROVIDER",
|
|
82
|
+
"UV_HTTP_TIMEOUT",
|
|
83
|
+
"UV_HTTP_CONNECT_TIMEOUT",
|
|
84
|
+
"UV_HTTP_RETRIES",
|
|
85
|
+
"UV_PYTHON_INSTALL_MIRROR",
|
|
86
|
+
"UV_PYPY_INSTALL_MIRROR",
|
|
87
|
+
"SSL_CERT_FILE",
|
|
88
|
+
"SSL_CERT_DIR",
|
|
89
|
+
"SSL_CLIENT_CERT",
|
|
90
|
+
"REQUESTS_CA_BUNDLE",
|
|
91
|
+
"CURL_CA_BUNDLE",
|
|
92
|
+
"NETRC"
|
|
93
|
+
];
|
|
94
|
+
/** Installer-only download settings; never use this environment for provider imports. */
|
|
95
|
+
function installerEnvironment(source = process.env) {
|
|
96
|
+
const env = workerEnvironment(source);
|
|
97
|
+
for (const key of INSTALLER_NETWORK_KEYS) if (source[key] !== void 0) env[key] = source[key];
|
|
98
|
+
for (const key of Object.keys(source)) if (/^UV_INDEX_[A-Z0-9_]+_(USERNAME|PASSWORD)$/.test(key) && source[key] !== void 0) env[key] = source[key];
|
|
99
|
+
return env;
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/installer.ts
|
|
103
|
+
/** Only fixed, intentionally public installer guidance uses this error type. */
|
|
104
|
+
var InstallerError = class extends Error {
|
|
105
|
+
name = "InstallerError";
|
|
106
|
+
};
|
|
107
|
+
const PYTHON_COMPATIBLE = "import os, sys; assert (3,12,4) <= sys.version_info[:3] < (3,15,0); assert all(hasattr(os, name) for name in ('fork', 'setsid', 'waitid', 'WNOWAIT', 'WEXITED', 'WNOHANG', 'P_PID'))";
|
|
108
|
+
const VERIFY = `${PYTHON_COMPATIBLE}; import importlib.metadata as m; assert m.version('mcp-server-linkedin') == '${EXTRACTOR_VERSION}'; assert m.version('patchright') == '${PATCHRIGHT_VERSION}'; assert m.version('python-dotenv') == '${DOTENV_VERSION}'; from linkedin_mcp_server.scraping.extractor import LinkedInExtractor; from patchright.async_api import async_playwright; print('ready')`;
|
|
109
|
+
const SUPERVISOR_SCRIPT = fileURLToPath(new URL("../python/supervisor.py", import.meta.url));
|
|
110
|
+
const lockfile = createRequire(import.meta.url)("proper-lockfile");
|
|
111
|
+
function executeCommand(command, args, env, timeout) {
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
execFile(command, args, {
|
|
114
|
+
env,
|
|
115
|
+
timeout,
|
|
116
|
+
maxBuffer: 512 * 1024,
|
|
117
|
+
encoding: "utf8"
|
|
118
|
+
}, (error, stdout) => {
|
|
119
|
+
resolve({
|
|
120
|
+
ok: !error,
|
|
121
|
+
stdout
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const runCommand = (command, args) => executeCommand(command, args, installerEnvironment(), 18e4);
|
|
127
|
+
const runReadinessCommand = (command, args) => executeCommand(command, args, workerEnvironment(), 15e3);
|
|
128
|
+
async function checkRuntime(options = {}) {
|
|
129
|
+
const run = options.run ?? runReadinessCommand;
|
|
130
|
+
const python = options.python ?? runtimePython(join(options.stateDir ?? stateDirectory(), RUNTIME_ID));
|
|
131
|
+
const supervisor = await run(python, [
|
|
132
|
+
"-I",
|
|
133
|
+
SUPERVISOR_SCRIPT,
|
|
134
|
+
"--check"
|
|
135
|
+
]);
|
|
136
|
+
if (!supervisor.ok || supervisor.stdout.trim() !== "ready") return false;
|
|
137
|
+
const result = await run(python, [
|
|
138
|
+
"-I",
|
|
139
|
+
"-c",
|
|
140
|
+
VERIFY
|
|
141
|
+
]);
|
|
142
|
+
return result.ok && result.stdout.trim() === "ready";
|
|
143
|
+
}
|
|
144
|
+
/** Retain the latest recoverable environment, without following or removing symlinks. */
|
|
145
|
+
async function prunePreviousRuntimes(stateDir, assertLease) {
|
|
146
|
+
assertLease();
|
|
147
|
+
const prefix = `${RUNTIME_ID}.previous-`;
|
|
148
|
+
const previous = (await readdir(stateDir, { withFileTypes: true })).flatMap((entry) => {
|
|
149
|
+
if (!entry.isDirectory() || !entry.name.startsWith(prefix)) return [];
|
|
150
|
+
const suffix = entry.name.slice(prefix.length);
|
|
151
|
+
const timestamp = Number(suffix);
|
|
152
|
+
if (!/^(0|[1-9]\d*)$/.test(suffix) || !Number.isSafeInteger(timestamp)) return [];
|
|
153
|
+
return [{
|
|
154
|
+
name: entry.name,
|
|
155
|
+
timestamp
|
|
156
|
+
}];
|
|
157
|
+
}).sort((left, right) => right.timestamp - left.timestamp);
|
|
158
|
+
for (const entry of previous.slice(1)) {
|
|
159
|
+
assertLease();
|
|
160
|
+
const path = join(stateDir, entry.name);
|
|
161
|
+
const metadata = await lstat(path).catch((error) => {
|
|
162
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
|
|
163
|
+
throw error;
|
|
164
|
+
});
|
|
165
|
+
if (metadata?.isDirectory() && !metadata.isSymbolicLink()) await rm(path, {
|
|
166
|
+
recursive: true,
|
|
167
|
+
force: true
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** One immutable dependency environment; never creates/downloads a browser or touches a profile. */
|
|
172
|
+
async function setupRuntime(options = {}) {
|
|
173
|
+
if (process.platform !== "linux" && process.platform !== "darwin") throw new InstallerError("LinkedIn local currently supports Linux and macOS only.");
|
|
174
|
+
const stateDir = options.stateDir ?? stateDirectory();
|
|
175
|
+
const run = options.run ?? runCommand;
|
|
176
|
+
const readinessRun = options.run ?? runReadinessCommand;
|
|
177
|
+
await mkdir(stateDir, {
|
|
178
|
+
recursive: true,
|
|
179
|
+
mode: 448
|
|
180
|
+
});
|
|
181
|
+
await chmod(stateDir, 448);
|
|
182
|
+
const lock = join(stateDir, "setup.lock");
|
|
183
|
+
let compromised = false;
|
|
184
|
+
const assertLease = () => {
|
|
185
|
+
if (compromised) throw new InstallerError("LinkedIn setup lost its installation lease. Check runtime health and retry setup after any other installation finishes.");
|
|
186
|
+
};
|
|
187
|
+
let release;
|
|
188
|
+
try {
|
|
189
|
+
release = await lockfile.lock(stateDir, {
|
|
190
|
+
lockfilePath: lock,
|
|
191
|
+
stale: 3e4,
|
|
192
|
+
update: 5e3,
|
|
193
|
+
retries: 0,
|
|
194
|
+
onCompromised: () => {
|
|
195
|
+
compromised = true;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
} catch {
|
|
199
|
+
throw new InstallerError("LinkedIn setup is already running. Wait for it to finish before reinstalling.");
|
|
200
|
+
}
|
|
201
|
+
let staging;
|
|
202
|
+
try {
|
|
203
|
+
if (await checkRuntime({
|
|
204
|
+
stateDir,
|
|
205
|
+
run: readinessRun
|
|
206
|
+
})) {
|
|
207
|
+
await prunePreviousRuntimes(stateDir, assertLease);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const uv = (await run("uv", ["--version"])).ok;
|
|
211
|
+
let python = options.python;
|
|
212
|
+
if (!uv || python) {
|
|
213
|
+
const candidates = python ? [python] : [
|
|
214
|
+
"python3.13",
|
|
215
|
+
"python3.14",
|
|
216
|
+
"python3.12",
|
|
217
|
+
"python3"
|
|
218
|
+
];
|
|
219
|
+
python = void 0;
|
|
220
|
+
for (const candidate of candidates) if ((await run(candidate, [
|
|
221
|
+
"-I",
|
|
222
|
+
"-c",
|
|
223
|
+
PYTHON_COMPATIBLE
|
|
224
|
+
])).ok) {
|
|
225
|
+
python = candidate;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
if (!python) throw new InstallerError("Install uv (provided on Alfe managed agents), or Python with venv and waitid/WNOWAIT support (Linux 3.12.4–3.14; macOS 3.13–3.14), then rerun LinkedIn setup.");
|
|
229
|
+
}
|
|
230
|
+
staging = await mkdtemp(join(stateDir, `${RUNTIME_ID}.install-`));
|
|
231
|
+
if (!(uv ? await run("uv", [
|
|
232
|
+
"venv",
|
|
233
|
+
"--python",
|
|
234
|
+
python ?? "3.13",
|
|
235
|
+
staging
|
|
236
|
+
]) : python ? await run(python, [
|
|
237
|
+
"-I",
|
|
238
|
+
"-m",
|
|
239
|
+
"venv",
|
|
240
|
+
staging
|
|
241
|
+
]) : { ok: false }).ok) throw new InstallerError("LinkedIn setup could not create its private Python environment. Check Python/uv availability and retry setup.");
|
|
242
|
+
const executable = runtimePython(staging);
|
|
243
|
+
const packages = [
|
|
244
|
+
`mcp-server-linkedin==${EXTRACTOR_VERSION}`,
|
|
245
|
+
`patchright==${PATCHRIGHT_VERSION}`,
|
|
246
|
+
`python-dotenv==${DOTENV_VERSION}`
|
|
247
|
+
];
|
|
248
|
+
if (!(uv ? await run("uv", [
|
|
249
|
+
"pip",
|
|
250
|
+
"install",
|
|
251
|
+
"--python",
|
|
252
|
+
executable,
|
|
253
|
+
...packages
|
|
254
|
+
]) : await run(executable, [
|
|
255
|
+
"-I",
|
|
256
|
+
"-m",
|
|
257
|
+
"pip",
|
|
258
|
+
"install",
|
|
259
|
+
"--disable-pip-version-check",
|
|
260
|
+
...packages
|
|
261
|
+
])).ok || !await checkRuntime({
|
|
262
|
+
python: executable,
|
|
263
|
+
run: readinessRun
|
|
264
|
+
})) throw new InstallerError("LinkedIn setup could not verify its pinned dependencies. Check package registry access and retry setup.");
|
|
265
|
+
assertLease();
|
|
266
|
+
const destination = join(stateDir, RUNTIME_ID);
|
|
267
|
+
if (await access(destination).then(() => true, () => false)) await rename(destination, join(stateDir, `${RUNTIME_ID}.previous-${String(Date.now())}`));
|
|
268
|
+
await rename(staging, destination);
|
|
269
|
+
staging = void 0;
|
|
270
|
+
await chmod(destination, 448);
|
|
271
|
+
if (!await checkRuntime({
|
|
272
|
+
stateDir,
|
|
273
|
+
run: readinessRun
|
|
274
|
+
})) throw new InstallerError("The installed LinkedIn runtime failed its final check. Retry setup to repair this private environment.");
|
|
275
|
+
await prunePreviousRuntimes(stateDir, assertLease);
|
|
276
|
+
} finally {
|
|
277
|
+
if (staging) await rm(staging, {
|
|
278
|
+
recursive: true,
|
|
279
|
+
force: true
|
|
280
|
+
});
|
|
281
|
+
await release();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
export { workerEnvironment as a, runtimePython as i, checkRuntime as n, workerScript as o, setupRuntime as r, InstallerError as t };
|
package/dist/plugin.cjs
ADDED
package/dist/plugin.d.ts
ADDED