@sellable/mcp 0.1.269 → 0.1.271
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-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/server.js +7 -8
- package/dist/tools/content-posts.d.ts +421 -1
- package/dist/tools/content-posts.js +802 -0
- package/dist/tools/engage-discovery.d.ts +24 -0
- package/dist/tools/engage-discovery.js +114 -9
- package/dist/tools/leads.js +1 -1
- package/dist/tools/registry.d.ts +76 -47
- package/dist/tools/registry.js +0 -2
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +0 -10
- package/skills/create-campaign/core/providers/prospeo.json +2 -5
- package/skills/create-post/SKILL.md +746 -36
- package/skills/create-post/references/hook-research-playbook.md +493 -31
- package/skills/create-post/references/linkedin-preview-rendering.md +221 -0
- package/skills/create-post/references/post-file-contract.md +41 -0
- package/skills/create-post/references/post-validation.md +288 -27
- package/skills/create-post/references/premise-development.md +298 -7
- package/skills/providers/prospeo.md +0 -21
- package/skills/research/config.json +9 -0
- package/dist/tools/harvest-jobs.d.ts +0 -182
- package/dist/tools/harvest-jobs.js +0 -429
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as os from "node:os";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { getApi } from "../api.js";
|
|
5
|
-
import { resolveWorkspaceRoot } from "../utils/workspace-root.js";
|
|
6
|
-
const entryPath = process.argv[1] ? path.resolve(process.argv[1]) : process.cwd();
|
|
7
|
-
const workspaceRoot = resolveWorkspaceRoot(path.dirname(entryPath));
|
|
8
|
-
const HARVEST_JOB_SEARCH_TOKEN_REF_PREFIX = "mcp-harvest-job-search-token:";
|
|
9
|
-
const MAX_HARVEST_JOB_SEARCH_TOKEN_REFS = 200;
|
|
10
|
-
const tokenRefs = new Map();
|
|
11
|
-
let tokenRefCounter = 0;
|
|
12
|
-
export const harvestJobToolDefinitions = [
|
|
13
|
-
{
|
|
14
|
-
name: "search_harvest_jobs",
|
|
15
|
-
description: "Search current LinkedIn jobs through Sellable's Harvest-backed v3 API, write markdown/CSV review artifacts, and return a token reference for selected-company confirmation. Use when account-source intent is current LinkedIn job posts. Does not call Prospeo or confirm companies.",
|
|
16
|
-
inputSchema: {
|
|
17
|
-
type: "object",
|
|
18
|
-
properties: {
|
|
19
|
-
search: { type: "string" },
|
|
20
|
-
searches: { type: "array", items: { type: "string" } },
|
|
21
|
-
location: { type: "string" },
|
|
22
|
-
geoId: { type: "string" },
|
|
23
|
-
postedLimit: { type: "string", enum: ["24h", "week", "month"] },
|
|
24
|
-
sortBy: { type: "string", enum: ["relevance", "date"] },
|
|
25
|
-
workplaceType: {
|
|
26
|
-
oneOf: [
|
|
27
|
-
{ type: "string" },
|
|
28
|
-
{ type: "array", items: { type: "string" } },
|
|
29
|
-
],
|
|
30
|
-
},
|
|
31
|
-
employmentType: {
|
|
32
|
-
oneOf: [
|
|
33
|
-
{ type: "string" },
|
|
34
|
-
{ type: "array", items: { type: "string" } },
|
|
35
|
-
],
|
|
36
|
-
},
|
|
37
|
-
experienceLevel: {
|
|
38
|
-
oneOf: [
|
|
39
|
-
{ type: "string" },
|
|
40
|
-
{ type: "array", items: { type: "string" } },
|
|
41
|
-
],
|
|
42
|
-
},
|
|
43
|
-
easyApply: { type: "boolean" },
|
|
44
|
-
under10Applicants: { type: "boolean" },
|
|
45
|
-
salary: { type: "string" },
|
|
46
|
-
pages: {
|
|
47
|
-
type: "number",
|
|
48
|
-
description: "Max pages per search term. Backend cap is 2.",
|
|
49
|
-
},
|
|
50
|
-
maxRows: {
|
|
51
|
-
type: "number",
|
|
52
|
-
description: "Max returned rows across searches. Backend cap is 100.",
|
|
53
|
-
},
|
|
54
|
-
artifactFormat: {
|
|
55
|
-
type: "string",
|
|
56
|
-
enum: ["markdown", "csv", "both"],
|
|
57
|
-
description: "Review artifact format. Defaults to markdown.",
|
|
58
|
-
},
|
|
59
|
-
outputDir: {
|
|
60
|
-
type: "string",
|
|
61
|
-
description: "Optional safe output directory under the workspace, home, or temp root.",
|
|
62
|
-
},
|
|
63
|
-
fileBaseName: {
|
|
64
|
-
type: "string",
|
|
65
|
-
description: "Optional safe artifact filename base without extension.",
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
required: [],
|
|
69
|
-
additionalProperties: false,
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: "confirm_harvest_job_companies",
|
|
74
|
-
description: "Confirm selected Harvest LinkedIn job IDs into a backend-resolved Prospeo domainFilterId, then guide the follow-on search_prospeo people search. Requires searchToken or token reference from search_harvest_jobs. Does not accept raw domains.",
|
|
75
|
-
inputSchema: {
|
|
76
|
-
type: "object",
|
|
77
|
-
properties: {
|
|
78
|
-
searchToken: {
|
|
79
|
-
type: "string",
|
|
80
|
-
description: "Signed search token or mcp-harvest-job-search-token:* reference from search_harvest_jobs.",
|
|
81
|
-
},
|
|
82
|
-
selectedJobIds: {
|
|
83
|
-
type: "array",
|
|
84
|
-
items: { type: "string" },
|
|
85
|
-
description: "Harvest job IDs selected from the search artifact.",
|
|
86
|
-
},
|
|
87
|
-
name: { type: "string" },
|
|
88
|
-
outputDir: {
|
|
89
|
-
type: "string",
|
|
90
|
-
description: "Optional safe output directory under the workspace, home, or temp root.",
|
|
91
|
-
},
|
|
92
|
-
fileBaseName: {
|
|
93
|
-
type: "string",
|
|
94
|
-
description: "Optional safe artifact filename base without extension.",
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
required: ["searchToken", "selectedJobIds"],
|
|
98
|
-
additionalProperties: false,
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
];
|
|
102
|
-
function removeUndefinedValues(input) {
|
|
103
|
-
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
|
|
104
|
-
}
|
|
105
|
-
function storeTokenRef(token) {
|
|
106
|
-
if (typeof token !== "string" || token.length === 0)
|
|
107
|
-
return token;
|
|
108
|
-
if (token.startsWith(HARVEST_JOB_SEARCH_TOKEN_REF_PREFIX))
|
|
109
|
-
return token;
|
|
110
|
-
const ref = `${HARVEST_JOB_SEARCH_TOKEN_REF_PREFIX}${Date.now().toString(36)}-${(++tokenRefCounter).toString(36)}`;
|
|
111
|
-
tokenRefs.set(ref, token);
|
|
112
|
-
while (tokenRefs.size > MAX_HARVEST_JOB_SEARCH_TOKEN_REFS) {
|
|
113
|
-
const oldest = tokenRefs.keys().next().value;
|
|
114
|
-
if (!oldest)
|
|
115
|
-
break;
|
|
116
|
-
tokenRefs.delete(oldest);
|
|
117
|
-
}
|
|
118
|
-
return ref;
|
|
119
|
-
}
|
|
120
|
-
function resolveTokenRef(token) {
|
|
121
|
-
if (!token.startsWith(HARVEST_JOB_SEARCH_TOKEN_REF_PREFIX))
|
|
122
|
-
return token;
|
|
123
|
-
const resolved = tokenRefs.get(token);
|
|
124
|
-
if (!resolved) {
|
|
125
|
-
throw new Error("confirm_harvest_job_companies received a stale Harvest job search token reference. Re-run search_harvest_jobs.");
|
|
126
|
-
}
|
|
127
|
-
return resolved;
|
|
128
|
-
}
|
|
129
|
-
function validateSearchInput(input) {
|
|
130
|
-
if (!input?.search && (!input?.searches || input.searches.length === 0)) {
|
|
131
|
-
throw new Error("search_harvest_jobs requires search or searches.");
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
function validateConfirmInput(input) {
|
|
135
|
-
if (!input?.searchToken) {
|
|
136
|
-
throw new Error("confirm_harvest_job_companies requires searchToken from search_harvest_jobs.");
|
|
137
|
-
}
|
|
138
|
-
if (!input.selectedJobIds || input.selectedJobIds.length === 0) {
|
|
139
|
-
throw new Error("confirm_harvest_job_companies requires selectedJobIds from search_harvest_jobs results.");
|
|
140
|
-
}
|
|
141
|
-
if ("domain" in input ||
|
|
142
|
-
"domains" in input ||
|
|
143
|
-
"includeDomains" in input ||
|
|
144
|
-
"companyDomains" in input) {
|
|
145
|
-
throw new Error("confirm_harvest_job_companies does not accept raw domains. Use searchToken and selectedJobIds.");
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
function decodeMaybe(value) {
|
|
149
|
-
try {
|
|
150
|
-
return decodeURIComponent(value);
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
return value;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
function isPathInside(candidate, root) {
|
|
157
|
-
const relative = path.relative(root, candidate);
|
|
158
|
-
return (relative === "" ||
|
|
159
|
-
(!relative.startsWith("..") && !path.isAbsolute(relative)));
|
|
160
|
-
}
|
|
161
|
-
function safeBaseName(value, fallback) {
|
|
162
|
-
const raw = (value ?? fallback).trim();
|
|
163
|
-
const decoded = decodeMaybe(raw);
|
|
164
|
-
const cleaned = decoded
|
|
165
|
-
.replace(/\.[A-Za-z0-9]+$/, "")
|
|
166
|
-
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
167
|
-
.replace(/^-+|-+$/g, "")
|
|
168
|
-
.slice(0, 80);
|
|
169
|
-
if (!cleaned || cleaned.includes("..")) {
|
|
170
|
-
throw new Error("fileBaseName must be a safe filename.");
|
|
171
|
-
}
|
|
172
|
-
return cleaned;
|
|
173
|
-
}
|
|
174
|
-
function allowedRoots() {
|
|
175
|
-
return [workspaceRoot, os.homedir(), os.tmpdir()]
|
|
176
|
-
.map((root) => {
|
|
177
|
-
const resolved = path.resolve(root);
|
|
178
|
-
try {
|
|
179
|
-
return fs.realpathSync(resolved);
|
|
180
|
-
}
|
|
181
|
-
catch {
|
|
182
|
-
return resolved;
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
.filter(Boolean);
|
|
186
|
-
}
|
|
187
|
-
function resolveOutputDir(outputDir) {
|
|
188
|
-
const target = outputDir
|
|
189
|
-
? path.isAbsolute(outputDir)
|
|
190
|
-
? path.resolve(outputDir)
|
|
191
|
-
: path.resolve(workspaceRoot, outputDir)
|
|
192
|
-
: path.resolve(workspaceRoot, ".sellable", "artifacts", "harvest-jobs");
|
|
193
|
-
if (target.includes("\0")) {
|
|
194
|
-
throw new Error("outputDir contains invalid characters.");
|
|
195
|
-
}
|
|
196
|
-
fs.mkdirSync(target, { recursive: true });
|
|
197
|
-
const real = fs.realpathSync(target);
|
|
198
|
-
if (!allowedRoots().some((root) => isPathInside(real, root))) {
|
|
199
|
-
throw new Error("outputDir must be inside the workspace, home directory, or temp directory.");
|
|
200
|
-
}
|
|
201
|
-
return real;
|
|
202
|
-
}
|
|
203
|
-
function collisionSafePath(outputDir, fileBaseName, ext) {
|
|
204
|
-
const extension = ext.startsWith(".") ? ext : `.${ext}`;
|
|
205
|
-
if (![".md", ".csv"].includes(extension)) {
|
|
206
|
-
throw new Error(`Unsupported artifact extension: ${extension}`);
|
|
207
|
-
}
|
|
208
|
-
let candidate = path.join(outputDir, `${fileBaseName}${extension}`);
|
|
209
|
-
let counter = 1;
|
|
210
|
-
while (fs.existsSync(candidate)) {
|
|
211
|
-
candidate = path.join(outputDir, `${fileBaseName}-${counter}${extension}`);
|
|
212
|
-
counter += 1;
|
|
213
|
-
}
|
|
214
|
-
const resolved = path.resolve(candidate);
|
|
215
|
-
if (!isPathInside(resolved, outputDir)) {
|
|
216
|
-
throw new Error("Artifact path escapes outputDir.");
|
|
217
|
-
}
|
|
218
|
-
if (fs.existsSync(resolved) && fs.lstatSync(resolved).isSymbolicLink()) {
|
|
219
|
-
throw new Error("Refusing to write through symlink artifact path.");
|
|
220
|
-
}
|
|
221
|
-
return resolved;
|
|
222
|
-
}
|
|
223
|
-
function csvEscape(value) {
|
|
224
|
-
const text = value === null || value === undefined ? "" : String(value);
|
|
225
|
-
return /[",\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
226
|
-
}
|
|
227
|
-
function formatValue(value) {
|
|
228
|
-
if (value === null || value === undefined)
|
|
229
|
-
return "";
|
|
230
|
-
if (typeof value === "object")
|
|
231
|
-
return JSON.stringify(value);
|
|
232
|
-
return String(value);
|
|
233
|
-
}
|
|
234
|
-
function renderSearchMarkdown(response) {
|
|
235
|
-
const rows = Array.isArray(response.rows) ? response.rows : [];
|
|
236
|
-
const lines = [
|
|
237
|
-
"# Harvest LinkedIn Job Search",
|
|
238
|
-
"",
|
|
239
|
-
`Row count: ${response.rowCount ?? rows.length}`,
|
|
240
|
-
`Cost summary: ${JSON.stringify(response.costSummary ?? {})}`,
|
|
241
|
-
`Search summary: ${JSON.stringify(response.summary ?? {})}`,
|
|
242
|
-
"",
|
|
243
|
-
"Domains are resolved only during confirmation. Select stable Harvest job IDs, not LinkedIn company URLs.",
|
|
244
|
-
"",
|
|
245
|
-
"| Job ID | Company | Title | Posted | Location | Easy Apply | Job URL | Company LinkedIn | Request ID |",
|
|
246
|
-
"| --- | --- | --- | --- | --- | --- | --- | --- | --- |",
|
|
247
|
-
...rows.map((row) => [
|
|
248
|
-
row.jobId,
|
|
249
|
-
row.companyName,
|
|
250
|
-
row.title,
|
|
251
|
-
row.postedDate,
|
|
252
|
-
row.locationText,
|
|
253
|
-
row.easyApply,
|
|
254
|
-
row.jobUrl,
|
|
255
|
-
row.companyLinkedinUrl,
|
|
256
|
-
row.harvestRequestId,
|
|
257
|
-
]
|
|
258
|
-
.map((value) => String(value ?? "").replace(/\|/g, "\\|"))
|
|
259
|
-
.join(" | ")
|
|
260
|
-
.replace(/^/, "| ")
|
|
261
|
-
.replace(/$/, " |")),
|
|
262
|
-
"",
|
|
263
|
-
"Next step: confirm_harvest_job_companies({ searchToken, selectedJobIds: [...] })",
|
|
264
|
-
];
|
|
265
|
-
return `${lines.join("\n")}\n`;
|
|
266
|
-
}
|
|
267
|
-
function renderSearchCsv(response) {
|
|
268
|
-
const rows = Array.isArray(response.rows) ? response.rows : [];
|
|
269
|
-
const columns = [
|
|
270
|
-
"rowId",
|
|
271
|
-
"jobId",
|
|
272
|
-
"companyName",
|
|
273
|
-
"title",
|
|
274
|
-
"jobUrl",
|
|
275
|
-
"postedDate",
|
|
276
|
-
"locationText",
|
|
277
|
-
"easyApply",
|
|
278
|
-
"companyLinkedinUrl",
|
|
279
|
-
"companyUniversalName",
|
|
280
|
-
"harvestRequestId",
|
|
281
|
-
"search",
|
|
282
|
-
"page",
|
|
283
|
-
];
|
|
284
|
-
return [
|
|
285
|
-
columns.join(","),
|
|
286
|
-
...rows.map((row) => columns.map((column) => csvEscape(row[column])).join(",")),
|
|
287
|
-
].join("\n");
|
|
288
|
-
}
|
|
289
|
-
function renderConfirmMarkdown(response) {
|
|
290
|
-
const unresolved = Array.isArray(response.unresolvedJobs)
|
|
291
|
-
? response.unresolvedJobs
|
|
292
|
-
: [];
|
|
293
|
-
const skipped = Array.isArray(response.skippedJobs) ? response.skippedJobs : [];
|
|
294
|
-
const lines = [
|
|
295
|
-
"# Harvest LinkedIn Job Confirmation",
|
|
296
|
-
"",
|
|
297
|
-
`Domain filter ID: ${response.domainFilterId ?? ""}`,
|
|
298
|
-
`Resolved: ${response.resolvedCount ?? 0}`,
|
|
299
|
-
`Unresolved: ${response.unresolvedCount ?? 0}`,
|
|
300
|
-
`Duplicates: ${response.duplicateCount ?? 0}`,
|
|
301
|
-
`Skipped: ${response.skippedCount ?? 0}`,
|
|
302
|
-
`Cost summary: ${JSON.stringify(response.costSummary ?? {})}`,
|
|
303
|
-
"",
|
|
304
|
-
"## Resolved Domains",
|
|
305
|
-
"",
|
|
306
|
-
...(response.includeDomains ?? []).map((domain) => `- ${domain}`),
|
|
307
|
-
"",
|
|
308
|
-
"## Unresolved Jobs",
|
|
309
|
-
"",
|
|
310
|
-
...unresolved.map((row) => `- ${formatValue(row.jobId)}: ${formatValue(row.reason)}`),
|
|
311
|
-
"",
|
|
312
|
-
"## Skipped Jobs",
|
|
313
|
-
"",
|
|
314
|
-
...skipped.map((row) => `- ${formatValue(row.jobId)}: ${formatValue(row.reason)}`),
|
|
315
|
-
"",
|
|
316
|
-
"Next step: search_prospeo with the returned domainFilterId.",
|
|
317
|
-
];
|
|
318
|
-
return `${lines.join("\n")}\n`;
|
|
319
|
-
}
|
|
320
|
-
function writeSearchArtifacts(response, input) {
|
|
321
|
-
const format = input.artifactFormat ?? "markdown";
|
|
322
|
-
const outputDir = resolveOutputDir(input.outputDir);
|
|
323
|
-
const baseName = safeBaseName(input.fileBaseName, `harvest-jobs-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
324
|
-
const artifacts = {};
|
|
325
|
-
if (format === "markdown" || format === "both") {
|
|
326
|
-
const markdownPath = collisionSafePath(outputDir, baseName, ".md");
|
|
327
|
-
fs.writeFileSync(markdownPath, renderSearchMarkdown(response), "utf8");
|
|
328
|
-
artifacts.markdown = markdownPath;
|
|
329
|
-
}
|
|
330
|
-
if (format === "csv" || format === "both") {
|
|
331
|
-
const csvPath = collisionSafePath(outputDir, baseName, ".csv");
|
|
332
|
-
fs.writeFileSync(csvPath, renderSearchCsv(response), "utf8");
|
|
333
|
-
artifacts.csv = csvPath;
|
|
334
|
-
}
|
|
335
|
-
return artifacts;
|
|
336
|
-
}
|
|
337
|
-
function writeConfirmArtifact(response, input) {
|
|
338
|
-
const outputDir = resolveOutputDir(input.outputDir);
|
|
339
|
-
const baseName = safeBaseName(input.fileBaseName, `harvest-jobs-confirm-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
340
|
-
const artifactPath = collisionSafePath(outputDir, baseName, ".md");
|
|
341
|
-
fs.writeFileSync(artifactPath, renderConfirmMarkdown(response), "utf8");
|
|
342
|
-
return artifactPath;
|
|
343
|
-
}
|
|
344
|
-
function compactSearchResponse(response, artifacts) {
|
|
345
|
-
const rows = Array.isArray(response.rows) ? response.rows : [];
|
|
346
|
-
const token = storeTokenRef(response.searchToken);
|
|
347
|
-
return removeUndefinedValues({
|
|
348
|
-
success: response.success ?? true,
|
|
349
|
-
rowCount: typeof response.rowCount === "number" ? response.rowCount : rows.length,
|
|
350
|
-
costSummary: response.costSummary ?? {},
|
|
351
|
-
artifacts,
|
|
352
|
-
sampleJobs: rows.slice(0, 10).map((row) => removeUndefinedValues({
|
|
353
|
-
rowId: row.rowId,
|
|
354
|
-
jobId: row.jobId,
|
|
355
|
-
company: row.companyName,
|
|
356
|
-
title: row.title,
|
|
357
|
-
jobUrl: row.jobUrl,
|
|
358
|
-
postedDate: row.postedDate,
|
|
359
|
-
location: row.locationText,
|
|
360
|
-
easyApply: row.easyApply,
|
|
361
|
-
companyLinkedinUrl: row.companyLinkedinUrl,
|
|
362
|
-
companyUniversalName: row.companyUniversalName,
|
|
363
|
-
})),
|
|
364
|
-
searchToken: token,
|
|
365
|
-
nextStep: "Review the artifact, then call confirm_harvest_job_companies with searchToken and selectedJobIds. Harvest jobs are account-source evidence; Prospeo is the follow-on people search.",
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
function compactConfirmResponse(response, artifactPath) {
|
|
369
|
-
return removeUndefinedValues({
|
|
370
|
-
success: response.success ?? true,
|
|
371
|
-
domainFilterId: response.domainFilterId,
|
|
372
|
-
includeDomains: response.includeDomains,
|
|
373
|
-
resolvedCount: response.resolvedCount,
|
|
374
|
-
unresolvedCount: response.unresolvedCount,
|
|
375
|
-
duplicateCount: response.duplicateCount,
|
|
376
|
-
skippedCount: response.skippedCount,
|
|
377
|
-
costSummary: response.costSummary,
|
|
378
|
-
unresolvedJobs: response.unresolvedJobs ?? [],
|
|
379
|
-
skippedJobs: response.skippedJobs ?? [],
|
|
380
|
-
carryoverSummary: response.carryoverSummary,
|
|
381
|
-
confirmArtifactPath: artifactPath,
|
|
382
|
-
suggestedNextCall: {
|
|
383
|
-
tool: "search_prospeo",
|
|
384
|
-
arguments: response.nextSearchProspeoCall ?? {
|
|
385
|
-
domainFilterId: response.domainFilterId,
|
|
386
|
-
filters: { max_person_per_company: 1 },
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
export async function searchHarvestJobs(input) {
|
|
392
|
-
validateSearchInput(input);
|
|
393
|
-
const api = getApi();
|
|
394
|
-
const response = await api.post("/api/v3/harvest/linkedin-jobs/search", removeUndefinedValues({
|
|
395
|
-
search: input.search,
|
|
396
|
-
searches: input.searches,
|
|
397
|
-
location: input.location,
|
|
398
|
-
geoId: input.geoId,
|
|
399
|
-
postedLimit: input.postedLimit,
|
|
400
|
-
sortBy: input.sortBy,
|
|
401
|
-
workplaceType: input.workplaceType,
|
|
402
|
-
employmentType: input.employmentType,
|
|
403
|
-
experienceLevel: input.experienceLevel,
|
|
404
|
-
easyApply: input.easyApply,
|
|
405
|
-
under10Applicants: input.under10Applicants,
|
|
406
|
-
salary: input.salary,
|
|
407
|
-
pages: input.pages,
|
|
408
|
-
maxRows: input.maxRows,
|
|
409
|
-
}));
|
|
410
|
-
if (!response || typeof response !== "object" || !response.searchToken) {
|
|
411
|
-
throw new Error("search_harvest_jobs API response did not include a searchToken.");
|
|
412
|
-
}
|
|
413
|
-
const artifacts = writeSearchArtifacts(response, input);
|
|
414
|
-
return compactSearchResponse(response, artifacts);
|
|
415
|
-
}
|
|
416
|
-
export async function confirmHarvestJobCompanies(input) {
|
|
417
|
-
validateConfirmInput(input);
|
|
418
|
-
const api = getApi();
|
|
419
|
-
const response = await api.post("/api/v3/harvest/linkedin-jobs/confirm-domain-filter", removeUndefinedValues({
|
|
420
|
-
searchToken: resolveTokenRef(input.searchToken),
|
|
421
|
-
selectedJobIds: input.selectedJobIds,
|
|
422
|
-
name: input.name,
|
|
423
|
-
}));
|
|
424
|
-
if (!response || typeof response !== "object" || !response.domainFilterId) {
|
|
425
|
-
throw new Error("confirm_harvest_job_companies API response did not include a domainFilterId.");
|
|
426
|
-
}
|
|
427
|
-
const artifactPath = writeConfirmArtifact(response, input);
|
|
428
|
-
return compactConfirmResponse(response, artifactPath);
|
|
429
|
-
}
|