@curviate/cli 0.1.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -8,6 +8,20 @@ a new command or flag is a minor; a breaking command/flag/exit-code change is a
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.2.0] - 2026-06-24
12
+
13
+ ### Fixed
14
+
15
+ - `message inmail` now requires and forwards the `--surface` flag (was silently dropped).
16
+ - `connect respond` now requires and forwards `--shared-secret` (was silently dropped).
17
+ - `recruiter message new` uses the correct field name `attendee_ids` (was `attendees`).
18
+ - `recruiter job create` now forwards the full job body via JSON and scalar flags (was a no-op).
19
+
20
+ ### Added
21
+
22
+ - `search` and `recruiter search` / `sales-navigator search` accept `--filters` for raw JSON filter objects and named filter flags (`--title`, `--company`, `--location`, `--school`, `--industry`) for common parameters.
23
+ - `search` and `recruiter search` / `sales-navigator search` accept `--url` (profile URL filter) and `--keywords`.
24
+
11
25
  ## [0.1.0] - 2026-06-22
12
26
 
13
27
  ### Added
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/search-filters.ts
4
+ import { readFile } from "fs/promises";
5
+ async function readStdin() {
6
+ const chunks = [];
7
+ for await (const chunk of process.stdin) {
8
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
9
+ }
10
+ return Buffer.concat(chunks).toString("utf8");
11
+ }
12
+ var DEFAULT_FILTER_READERS = {
13
+ readFile: (path) => readFile(path, "utf8"),
14
+ readStdin
15
+ };
16
+ async function assembleFilters(flags, readers = DEFAULT_FILTER_READERS) {
17
+ let raw;
18
+ let source;
19
+ if (flags["filters-file"] !== void 0) {
20
+ source = `--filters-file ${flags["filters-file"]}`;
21
+ try {
22
+ raw = await readers.readFile(flags["filters-file"]);
23
+ } catch {
24
+ return { error: `cannot read ${source}` };
25
+ }
26
+ } else if (flags.filters === "-") {
27
+ source = "--filters - (stdin)";
28
+ raw = await readers.readStdin();
29
+ } else if (flags.filters !== void 0) {
30
+ source = "--filters";
31
+ raw = flags.filters;
32
+ }
33
+ if (raw === void 0) return { body: {} };
34
+ let parsed;
35
+ try {
36
+ parsed = JSON.parse(raw);
37
+ } catch {
38
+ return { error: `${source} is not valid JSON` };
39
+ }
40
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
41
+ return { error: `${source} must be a JSON object` };
42
+ }
43
+ return { body: parsed };
44
+ }
45
+ function splitCsv(value) {
46
+ return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
47
+ }
48
+ function splitCsvNumbers(value) {
49
+ return splitCsv(value).map((s) => Number(s)).filter((n) => Number.isFinite(n));
50
+ }
51
+
52
+ export {
53
+ DEFAULT_FILTER_READERS,
54
+ assembleFilters,
55
+ splitCsv,
56
+ splitCsvNumbers
57
+ };
package/dist/cli.js CHANGED
@@ -141,15 +141,15 @@ var main = defineCommand({
141
141
  // ---------------------------------------------------------------------------
142
142
  profile: () => import("./profile-WXA3NC7D.js").then((m) => m.profileCommand),
143
143
  company: () => import("./company-IKVDW7MX.js").then((m) => m.companyCommand),
144
- connect: () => import("./connect-HLDHFBGX.js").then((m) => m.connectCommand),
145
- search: () => import("./search-LHPTHPWH.js").then((m) => m.searchCommand),
144
+ connect: () => import("./connect-XPXYKOII.js").then((m) => m.connectCommand),
145
+ search: () => import("./search-XJMVHZIZ.js").then((m) => m.searchCommand),
146
146
  inbox: () => import("./inbox-JBLYJMV2.js").then((m) => m.inboxCommand),
147
- message: () => import("./message-IK7VGB63.js").then((m) => m.messageCommand),
147
+ message: () => import("./message-SFP2I7QE.js").then((m) => m.messageCommand),
148
148
  post: () => import("./post-EAAGVR5D.js").then((m) => m.postCommand),
149
149
  account: () => import("./account-YOW2MCZS.js").then((m) => m.accountCommand),
150
150
  webhook: () => import("./webhook-QM5LGAUF.js").then((m) => m.webhookCommand),
151
- "sales-nav": () => import("./sales-nav-XGFO5I6F.js").then((m) => m.salesNavCommand),
152
- recruiter: () => import("./recruiter-TGCV36EH.js").then((m) => m.recruiterCommand)
151
+ "sales-nav": () => import("./sales-nav-F5VK7CPD.js").then((m) => m.salesNavCommand),
152
+ recruiter: () => import("./recruiter-D4BO4NO4.js").then((m) => m.recruiterCommand)
153
153
  },
154
154
  async run() {
155
155
  const { runMain } = await import("citty");
@@ -155,11 +155,19 @@ async function runConnectRespond(client, flags, out) {
155
155
  const accountId = requireAccount(flags.account, out);
156
156
  const invitationId = flags.id ?? "";
157
157
  const action = flags.action ?? "";
158
+ const sharedSecret = flags["shared-secret"] ?? "";
159
+ if (!sharedSecret) {
160
+ out.stderr.write(
161
+ "error: --shared-secret is required. Read it from `connect received` (each item carries its per-invitation shared_secret).\n"
162
+ );
163
+ process.exit(2);
164
+ }
165
+ const body = { action, shared_secret: sharedSecret };
158
166
  if (flags.preview) {
159
167
  const preview = buildPreviewOutput({
160
168
  method: "invites.respond",
161
169
  args: { invitation_id: invitationId },
162
- body: { action },
170
+ body,
163
171
  account: accountId
164
172
  });
165
173
  out.stdout.write(JSON.stringify(preview) + "\n");
@@ -168,7 +176,7 @@ async function runConnectRespond(client, flags, out) {
168
176
  const ns = client.account(accountId);
169
177
  const outOpts = resolveOutputOpts(flags);
170
178
  try {
171
- const result = await ns.invites.respond(invitationId, { action });
179
+ const result = await ns.invites.respond(invitationId, body);
172
180
  renderSuccess(result, outOpts, out);
173
181
  } catch (err) {
174
182
  const { CurviateError } = await import("@curviate/sdk");
@@ -232,7 +240,10 @@ var connectSentCommand = defineCommand({
232
240
  }
233
241
  });
234
242
  var connectReceivedCommand = defineCommand({
235
- meta: { name: "received", description: "List received connection invitations." },
243
+ meta: {
244
+ name: "received",
245
+ description: "List received connection invitations. Each item carries a shared_secret \u2014 pass it to `connect respond --shared-secret`."
246
+ },
236
247
  args: { ...GLOBAL_FLAGS },
237
248
  async run({ args }) {
238
249
  const flags = args;
@@ -257,7 +268,12 @@ var connectRespondCommand = defineCommand({
257
268
  args: {
258
269
  ...GLOBAL_FLAGS,
259
270
  id: { type: "positional", description: "Invitation id to respond to." },
260
- action: { type: "string", description: "Response action: accept or decline.", required: true }
271
+ action: { type: "string", description: "Response action: accept or decline.", required: true },
272
+ "shared-secret": {
273
+ type: "string",
274
+ description: "Per-invitation shared secret \u2014 read it from `connect received`.",
275
+ required: true
276
+ }
261
277
  },
262
278
  async run({ args }) {
263
279
  const flags = args;
@@ -62,6 +62,8 @@ function normalizeAttachPaths(attach) {
62
62
  if (!attach) return [];
63
63
  return Array.isArray(attach) ? attach : [attach];
64
64
  }
65
+ var INMAIL_SURFACES = ["sales_nav", "recruiter"];
66
+ var MEMBER_URN_RE = /^urn:li:member:\d+$/;
65
67
  async function handleSdkError(err, outOpts, out) {
66
68
  const { CurviateError } = await import("@curviate/sdk");
67
69
  if (err instanceof CurviateError) {
@@ -268,11 +270,26 @@ async function runMessageAttachment(client, flags, out, isTTY) {
268
270
  }
269
271
  async function runMessageInMail(client, flags, out) {
270
272
  const accountId = requireAccount(flags.account, out);
273
+ const surface = flags.surface ?? "";
274
+ if (!INMAIL_SURFACES.includes(surface)) {
275
+ out.stderr.write(
276
+ `error: --surface is required and must be one of ${INMAIL_SURFACES.join(", ")}.
277
+ `
278
+ );
279
+ process.exit(2);
280
+ }
271
281
  const recipientUrn = resolveIdentifier(flags.to ?? "");
282
+ if (!MEMBER_URN_RE.test(recipientUrn)) {
283
+ out.stderr.write(
284
+ "error: --to must be a LinkedIn member URN (e.g. urn:li:member:99999), not a URL or slug.\n"
285
+ );
286
+ process.exit(2);
287
+ }
272
288
  const subject = flags.subject ?? "";
273
289
  const text = flags.text ?? "";
274
290
  const body = {
275
291
  recipient_urn: recipientUrn,
292
+ surface,
276
293
  subject,
277
294
  text
278
295
  };
@@ -467,7 +484,8 @@ var messageInMailCommand = defineCommand({
467
484
  meta: { name: "inmail", description: "Send an InMail to a member." },
468
485
  args: {
469
486
  ...GLOBAL_FLAGS,
470
- to: { type: "string", description: "Recipient LinkedIn URN, URL, or slug.", required: true },
487
+ to: { type: "string", description: "Recipient member URN (urn:li:member:<id>). Must be a URN, not a URL or slug.", required: true },
488
+ surface: { type: "string", description: "InMail surface: sales_nav or recruiter.", required: true },
471
489
  subject: { type: "string", description: "InMail subject line.", required: true },
472
490
  text: { type: "positional", description: "InMail body text." }
473
491
  },
@@ -13,6 +13,11 @@ import {
13
13
  import {
14
14
  resolveIdentifier
15
15
  } from "./chunk-BNUTM6KD.js";
16
+ import {
17
+ DEFAULT_FILTER_READERS,
18
+ assembleFilters,
19
+ splitCsv
20
+ } from "./chunk-42VUUKQ3.js";
16
21
  import {
17
22
  streamAll
18
23
  } from "./chunk-SND3NHCT.js";
@@ -29,6 +34,7 @@ import {
29
34
 
30
35
  // src/commands/recruiter.ts
31
36
  import { defineCommand } from "citty";
37
+ import { readFile } from "fs/promises";
32
38
  function buildOutputStreams() {
33
39
  return {
34
40
  stdout: { write: (s) => process.stdout.write(s) },
@@ -69,6 +75,51 @@ async function handleSdkError(err, outOpts, out) {
69
75
  renderUnexpectedError(err, out);
70
76
  process.exit(1);
71
77
  }
78
+ async function readStdin() {
79
+ const chunks = [];
80
+ for await (const chunk of process.stdin) {
81
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
82
+ }
83
+ return Buffer.concat(chunks).toString("utf8");
84
+ }
85
+ var DEFAULT_JOB_CREATE_READERS = {
86
+ readFile: (path) => readFile(path, "utf8"),
87
+ readStdin
88
+ };
89
+ async function assembleJobCreateBody(flags, readers) {
90
+ let base = {};
91
+ let raw;
92
+ let source;
93
+ if (flags["body-file"] !== void 0) {
94
+ source = `--body-file ${flags["body-file"]}`;
95
+ try {
96
+ raw = await readers.readFile(flags["body-file"]);
97
+ } catch {
98
+ return { error: `cannot read ${source}` };
99
+ }
100
+ } else if (flags.body === "-") {
101
+ source = "--body - (stdin)";
102
+ raw = await readers.readStdin();
103
+ } else if (flags.body !== void 0) {
104
+ return { error: "--body only accepts '-' (read JSON from stdin); use --body-file <path> for a file." };
105
+ }
106
+ if (raw !== void 0) {
107
+ let parsed;
108
+ try {
109
+ parsed = JSON.parse(raw);
110
+ } catch {
111
+ return { error: `${source} is not valid JSON` };
112
+ }
113
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
114
+ return { error: `${source} must be a JSON object` };
115
+ }
116
+ base = parsed;
117
+ }
118
+ if (flags["job-title"] !== void 0) base["job_title"] = { text: flags["job-title"] };
119
+ if (flags.description !== void 0) base["description"] = flags.description;
120
+ if (flags["employment-type"] !== void 0) base["employment_type"] = flags["employment-type"];
121
+ return { body: base };
122
+ }
72
123
  async function runRecruiterSync(client, flags, out) {
73
124
  rejectPreviewOnRead(flags.preview, out);
74
125
  const accountId = requireAccount(flags.account, out);
@@ -107,13 +158,13 @@ async function runRecruiterMessageNew(client, flags, out) {
107
158
  throw err;
108
159
  }
109
160
  const body = {
110
- attendees_ids: [to],
161
+ attendee_ids: [to],
111
162
  text
112
163
  };
113
164
  if (flags.preview) {
114
165
  const preview = buildPreviewOutput({
115
166
  method: "recruiter.startChat",
116
- args: { attendees_ids: [to] },
167
+ args: { attendee_ids: [to] },
117
168
  body: { ...body },
118
169
  account: accountId,
119
170
  attachments: [
@@ -154,7 +205,7 @@ async function runRecruiterProfile(client, flags, out) {
154
205
  await handleSdkError(err, outOpts, out);
155
206
  }
156
207
  }
157
- async function runRecruiterSearchPeople(client, flags, out) {
208
+ async function runRecruiterSearchPeople(client, flags, out, readers = DEFAULT_FILTER_READERS) {
158
209
  rejectPreviewOnRead(flags.preview, out);
159
210
  const accountId = requireAccount(flags.account, out);
160
211
  const ns = client.account(accountId);
@@ -163,8 +214,18 @@ async function runRecruiterSearchPeople(client, flags, out) {
163
214
  const maxPages = flags["max-pages"] ? parseInt(flags["max-pages"], 10) : 100;
164
215
  const limit = flags.limit ? parseInt(flags.limit, 10) : void 0;
165
216
  const cursor = flags.cursor;
166
- const body = {};
217
+ const assembled = await assembleFilters(flags, readers);
218
+ if ("error" in assembled) {
219
+ out.stderr.write(`error: ${assembled.error}
220
+ `);
221
+ process.exit(2);
222
+ }
223
+ const body = assembled.body;
167
224
  if (flags.keywords) body["keywords"] = flags.keywords;
225
+ if (flags.locale) body["locale"] = flags.locale;
226
+ if (flags["employment-type"]) body["employment_type"] = splitCsv(flags["employment-type"]);
227
+ if (flags.function) body["function"] = splitCsv(flags.function);
228
+ if (flags["profile-language"]) body["profile_language"] = splitCsv(flags["profile-language"]);
168
229
  const params = {};
169
230
  if (limit !== void 0) params["limit"] = limit;
170
231
  if (cursor) params["cursor"] = cursor;
@@ -200,6 +261,8 @@ async function runRecruiterGetParameters(client, flags, out) {
200
261
  const outOpts = resolveOutputOpts(flags);
201
262
  const params = {};
202
263
  if (flags.type) params["type"] = flags.type;
264
+ if (flags.keywords) params["keywords"] = flags.keywords;
265
+ if (flags.limit) params["limit"] = parseInt(flags.limit, 10);
203
266
  try {
204
267
  const result = await ns.recruiter.getParameters(params);
205
268
  renderSuccess(result, outOpts, out);
@@ -353,10 +416,16 @@ async function runRecruiterListJobs(client, flags, out) {
353
416
  await handleSdkError(err, outOpts, out);
354
417
  }
355
418
  }
356
- async function runRecruiterCreateJob(client, flags, out) {
419
+ async function runRecruiterCreateJob(client, flags, out, readers = DEFAULT_JOB_CREATE_READERS) {
357
420
  const accountId = requireAccount(flags.account, out);
358
421
  const outOpts = resolveOutputOpts(flags);
359
- const body = {};
422
+ const assembled = await assembleJobCreateBody(flags, readers);
423
+ if ("error" in assembled) {
424
+ out.stderr.write(`error: ${assembled.error}
425
+ `);
426
+ process.exit(2);
427
+ }
428
+ const body = assembled.body;
360
429
  if (flags.preview) {
361
430
  const preview = buildPreviewOutput({
362
431
  method: "recruiter.createJob",
@@ -561,7 +630,13 @@ var recruiterSearchPeopleCommand = defineCommand({
561
630
  meta: { name: "people", description: "Search Recruiter member profiles." },
562
631
  args: {
563
632
  ...GLOBAL_FLAGS,
564
- keywords: { type: "string", description: "Keyword search string." }
633
+ keywords: { type: "string", description: "Keyword search string." },
634
+ filters: { type: "string", description: "Filter body as a JSON object (escape hatch for the full filter surface); '-' reads JSON from stdin." },
635
+ "filters-file": { type: "string", description: "Path to a JSON file with the filter body." },
636
+ locale: { type: "string", description: "Result locale, e.g. en." },
637
+ "employment-type": { type: "string", description: "Employment type ids (comma-separated)." },
638
+ function: { type: "string", description: "Job function ids (comma-separated)." },
639
+ "profile-language": { type: "string", description: "Profile language codes (comma-separated)." }
565
640
  },
566
641
  async run({ args }) {
567
642
  const flags = args;
@@ -585,7 +660,8 @@ var recruiterSearchParametersCommand = defineCommand({
585
660
  meta: { name: "parameters", description: "Resolve Recruiter filter parameter IDs." },
586
661
  args: {
587
662
  ...GLOBAL_FLAGS,
588
- type: { type: "string", description: "Parameter type (e.g. LOCATION, INDUSTRY, TITLE).", required: true }
663
+ type: { type: "string", description: "Parameter type (e.g. LOCATION, INDUSTRY, TITLE).", required: true },
664
+ keywords: { type: "string", description: "Human term to resolve (e.g. Berlin)." }
589
665
  },
590
666
  async run({ args }) {
591
667
  const flags = args;
@@ -768,7 +844,14 @@ var recruiterJobsCommand = defineCommand({
768
844
  });
769
845
  var recruiterJobCreateCommand = defineCommand({
770
846
  meta: { name: "create", description: "Create a Recruiter job posting draft." },
771
- args: { ...GLOBAL_FLAGS },
847
+ args: {
848
+ ...GLOBAL_FLAGS,
849
+ "body-file": { type: "string", description: "Path to a JSON file with the full job-create body." },
850
+ body: { type: "string", description: "Read the JSON job-create body from stdin (pass '-')." },
851
+ "job-title": { type: "string", description: "Job title text (merged over the JSON as job_title.text)." },
852
+ description: { type: "string", description: "Job description (merged over the JSON)." },
853
+ "employment-type": { type: "string", description: "Employment type, e.g. FULL_TIME (merged over the JSON)." }
854
+ },
772
855
  async run({ args }) {
773
856
  const flags = args;
774
857
  const cfg = await resolveEffectiveConfig({
@@ -9,6 +9,12 @@ import {
9
9
  import {
10
10
  resolveIdentifier
11
11
  } from "./chunk-BNUTM6KD.js";
12
+ import {
13
+ DEFAULT_FILTER_READERS,
14
+ assembleFilters,
15
+ splitCsv,
16
+ splitCsvNumbers
17
+ } from "./chunk-42VUUKQ3.js";
12
18
  import {
13
19
  streamAll
14
20
  } from "./chunk-SND3NHCT.js";
@@ -80,7 +86,7 @@ async function runSalesNavSync(client, flags, out) {
80
86
  await handleSdkError(err, outOpts, out);
81
87
  }
82
88
  }
83
- async function runSalesNavSearchPeople(client, flags, out) {
89
+ async function runSalesNavSearchPeople(client, flags, out, readers = DEFAULT_FILTER_READERS) {
84
90
  rejectPreviewOnRead(flags.preview, out);
85
91
  const accountId = requireAccount(flags.account, out);
86
92
  const ns = client.account(accountId);
@@ -89,8 +95,18 @@ async function runSalesNavSearchPeople(client, flags, out) {
89
95
  const maxPages = flags["max-pages"] ? parseInt(flags["max-pages"], 10) : 100;
90
96
  const limit = flags.limit ? parseInt(flags.limit, 10) : void 0;
91
97
  const cursor = flags.cursor;
92
- const body = {};
98
+ const assembled = await assembleFilters(flags, readers);
99
+ if ("error" in assembled) {
100
+ out.stderr.write(`error: ${assembled.error}
101
+ `);
102
+ process.exit(2);
103
+ }
104
+ const body = assembled.body;
93
105
  if (flags.keywords) body["keywords"] = flags.keywords;
106
+ if (flags["first-name"]) body["first_name"] = flags["first-name"];
107
+ if (flags["last-name"]) body["last_name"] = flags["last-name"];
108
+ if (flags.groups) body["groups"] = splitCsv(flags.groups);
109
+ if (flags["profile-language"]) body["profile_language"] = splitCsv(flags["profile-language"]);
94
110
  const params = {};
95
111
  if (limit !== void 0) params["limit"] = limit;
96
112
  if (cursor) params["cursor"] = cursor;
@@ -119,7 +135,7 @@ async function runSalesNavSearchPeople(client, flags, out) {
119
135
  await handleSdkError(err, outOpts, out);
120
136
  }
121
137
  }
122
- async function runSalesNavSearchCompanies(client, flags, out) {
138
+ async function runSalesNavSearchCompanies(client, flags, out, readers = DEFAULT_FILTER_READERS) {
123
139
  rejectPreviewOnRead(flags.preview, out);
124
140
  const accountId = requireAccount(flags.account, out);
125
141
  const ns = client.account(accountId);
@@ -128,8 +144,17 @@ async function runSalesNavSearchCompanies(client, flags, out) {
128
144
  const maxPages = flags["max-pages"] ? parseInt(flags["max-pages"], 10) : 100;
129
145
  const limit = flags.limit ? parseInt(flags.limit, 10) : void 0;
130
146
  const cursor = flags.cursor;
131
- const body = {};
147
+ const assembled = await assembleFilters(flags, readers);
148
+ if ("error" in assembled) {
149
+ out.stderr.write(`error: ${assembled.error}
150
+ `);
151
+ process.exit(2);
152
+ }
153
+ const body = assembled.body;
132
154
  if (flags.keywords) body["keywords"] = flags.keywords;
155
+ if (flags.technologies) body["technologies"] = splitCsv(flags.technologies);
156
+ if (flags["recent-activities"]) body["recent_activities"] = splitCsv(flags["recent-activities"]);
157
+ if (flags["network-distance"]) body["network_distance"] = splitCsvNumbers(flags["network-distance"]);
133
158
  const params = {};
134
159
  if (limit !== void 0) params["limit"] = limit;
135
160
  if (cursor) params["cursor"] = cursor;
@@ -165,6 +190,8 @@ async function runSalesNavGetParameters(client, flags, out) {
165
190
  const outOpts = resolveOutputOpts(flags);
166
191
  const params = {};
167
192
  if (flags.type) params["type"] = flags.type;
193
+ if (flags.keywords) params["keywords"] = flags.keywords;
194
+ if (flags.limit) params["limit"] = parseInt(flags.limit, 10);
168
195
  try {
169
196
  const result = await ns.salesNavigator.getParameters(params);
170
197
  renderSuccess(result, outOpts, out);
@@ -329,7 +356,13 @@ var salesNavSearchPeopleCommand = defineCommand({
329
356
  meta: { name: "people", description: "Search Sales Navigator member profiles." },
330
357
  args: {
331
358
  ...GLOBAL_FLAGS,
332
- keywords: { type: "string", description: "Keyword search string." }
359
+ keywords: { type: "string", description: "Keyword search string." },
360
+ filters: { type: "string", description: "Filter body as a JSON object (escape hatch for the full filter surface); '-' reads JSON from stdin." },
361
+ "filters-file": { type: "string", description: "Path to a JSON file with the filter body." },
362
+ "first-name": { type: "string", description: "First name to match." },
363
+ "last-name": { type: "string", description: "Last name to match." },
364
+ groups: { type: "string", description: "Group ids (comma-separated)." },
365
+ "profile-language": { type: "string", description: "Profile language codes (comma-separated)." }
333
366
  },
334
367
  async run({ args }) {
335
368
  const flags = args;
@@ -353,7 +386,12 @@ var salesNavSearchCompaniesCommand = defineCommand({
353
386
  meta: { name: "companies", description: "Search Sales Navigator companies." },
354
387
  args: {
355
388
  ...GLOBAL_FLAGS,
356
- keywords: { type: "string", description: "Keyword search string." }
389
+ keywords: { type: "string", description: "Keyword search string." },
390
+ filters: { type: "string", description: "Filter body as a JSON object (escape hatch for the full filter surface); '-' reads JSON from stdin." },
391
+ "filters-file": { type: "string", description: "Path to a JSON file with the filter body." },
392
+ technologies: { type: "string", description: "Technology tags (comma-separated)." },
393
+ "recent-activities": { type: "string", description: "Recent activity ids (comma-separated)." },
394
+ "network-distance": { type: "string", description: "Network distance, 1-3 (comma-separated)." }
357
395
  },
358
396
  async run({ args }) {
359
397
  const flags = args;
@@ -377,7 +415,8 @@ var salesNavSearchParametersCommand = defineCommand({
377
415
  meta: { name: "parameters", description: "Resolve Sales Navigator filter parameter IDs." },
378
416
  args: {
379
417
  ...GLOBAL_FLAGS,
380
- type: { type: "string", description: "Parameter type (e.g. LOCATION, INDUSTRY, TITLE).", required: true }
418
+ type: { type: "string", description: "Parameter type (e.g. LOCATION, INDUSTRY, TITLE).", required: true },
419
+ keywords: { type: "string", description: "Human term to resolve (e.g. Berlin)." }
381
420
  },
382
421
  async run({ args }) {
383
422
  const flags = args;
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_FILTER_READERS,
4
+ assembleFilters,
5
+ splitCsv,
6
+ splitCsvNumbers
7
+ } from "./chunk-42VUUKQ3.js";
2
8
  import {
3
9
  streamAll
4
10
  } from "./chunk-SND3NHCT.js";
@@ -15,6 +21,16 @@ import {
15
21
 
16
22
  // src/commands/search.ts
17
23
  import { defineCommand } from "citty";
24
+ var FILTER_FLAGS = {
25
+ filters: {
26
+ type: "string",
27
+ description: "Filter body as a JSON object (escape hatch for the full filter surface); '-' reads JSON from stdin."
28
+ },
29
+ "filters-file": {
30
+ type: "string",
31
+ description: "Path to a JSON file with the filter body."
32
+ }
33
+ };
18
34
  function buildOutputStreams() {
19
35
  return {
20
36
  stdout: { write: (s) => process.stdout.write(s) },
@@ -47,22 +63,66 @@ function resolveOutputOpts(flags) {
47
63
  fields: flags.fields
48
64
  };
49
65
  }
50
- function buildSearchBody(flags) {
51
- const body = {};
66
+ function applyCommonSearchFlags(body, flags) {
52
67
  if (flags.keywords) body["keywords"] = flags.keywords;
53
68
  if (flags.url) body["url"] = flags.url;
54
69
  if (flags.cursor) body["cursor"] = flags.cursor;
55
70
  if (flags.limit) body["limit"] = parseInt(flags.limit, 10);
56
- return body;
57
71
  }
58
- async function runSearchPeople(client, flags, out) {
72
+ var NAMED_FLAG_MAPPERS = {
73
+ people(body, flags) {
74
+ if (flags.industry) body["industry"] = splitCsv(flags.industry);
75
+ if (flags.location) body["location"] = splitCsv(flags.location);
76
+ if (flags.company) body["company"] = splitCsv(flags.company);
77
+ if (flags["past-company"]) body["past_company"] = splitCsv(flags["past-company"]);
78
+ if (flags.school) body["school"] = splitCsv(flags.school);
79
+ if (flags["network-distance"]) body["network_distance"] = splitCsvNumbers(flags["network-distance"]);
80
+ if (flags["connections-of"]) body["connections_of"] = flags["connections-of"];
81
+ if (flags["followers-of"]) body["followers_of"] = flags["followers-of"];
82
+ },
83
+ companies(body, flags) {
84
+ if (flags.industry) body["industry"] = splitCsv(flags.industry);
85
+ if (flags.location) body["location"] = splitCsv(flags.location);
86
+ if (flags["network-distance"]) body["network_distance"] = splitCsvNumbers(flags["network-distance"]);
87
+ },
88
+ posts(body, flags) {
89
+ if (flags["sort-by"]) body["sort_by"] = flags["sort-by"];
90
+ if (flags["date-posted"]) body["date_posted"] = flags["date-posted"];
91
+ if (flags["content-type"]) body["content_type"] = flags["content-type"];
92
+ },
93
+ jobs(body, flags) {
94
+ if (flags.location) body["location"] = splitCsv(flags.location);
95
+ if (flags.industry) body["industry"] = splitCsv(flags.industry);
96
+ if (flags.seniority) body["seniority"] = splitCsv(flags.seniority);
97
+ if (flags.function) body["function"] = splitCsv(flags.function);
98
+ if (flags["job-type"]) body["job_type"] = splitCsv(flags["job-type"]);
99
+ if (flags.company) body["company"] = splitCsv(flags.company);
100
+ if (flags["sort-by"]) body["sort_by"] = flags["sort-by"];
101
+ if (flags.region) body["region"] = flags.region;
102
+ }
103
+ };
104
+ async function buildSearchBody(kind, flags, readers) {
105
+ const assembled = await assembleFilters(flags, readers);
106
+ if ("error" in assembled) return assembled;
107
+ const body = assembled.body;
108
+ applyCommonSearchFlags(body, flags);
109
+ NAMED_FLAG_MAPPERS[kind](body, flags);
110
+ return { body };
111
+ }
112
+ async function runSearchPeople(client, flags, out, readers = DEFAULT_FILTER_READERS) {
59
113
  rejectPreviewOnRead(flags.preview, out);
60
114
  const accountId = requireAccount(flags.account, out);
61
115
  const ns = client.account(accountId);
62
116
  const outOpts = resolveOutputOpts(flags);
63
117
  const all = flags.all ?? false;
64
118
  const maxPages = flags["max-pages"] ? parseInt(flags["max-pages"], 10) : 100;
65
- const body = buildSearchBody(flags);
119
+ const assembled = await buildSearchBody("people", flags, readers);
120
+ if ("error" in assembled) {
121
+ out.stderr.write(`error: ${assembled.error}
122
+ `);
123
+ process.exit(2);
124
+ }
125
+ const body = assembled.body;
66
126
  try {
67
127
  if (all) {
68
128
  const fn = (p) => ns.search.people(p);
@@ -87,14 +147,20 @@ async function runSearchPeople(client, flags, out) {
87
147
  process.exit(1);
88
148
  }
89
149
  }
90
- async function runSearchCompanies(client, flags, out) {
150
+ async function runSearchCompanies(client, flags, out, readers = DEFAULT_FILTER_READERS) {
91
151
  rejectPreviewOnRead(flags.preview, out);
92
152
  const accountId = requireAccount(flags.account, out);
93
153
  const ns = client.account(accountId);
94
154
  const outOpts = resolveOutputOpts(flags);
95
155
  const all = flags.all ?? false;
96
156
  const maxPages = flags["max-pages"] ? parseInt(flags["max-pages"], 10) : 100;
97
- const body = buildSearchBody(flags);
157
+ const assembled = await buildSearchBody("companies", flags, readers);
158
+ if ("error" in assembled) {
159
+ out.stderr.write(`error: ${assembled.error}
160
+ `);
161
+ process.exit(2);
162
+ }
163
+ const body = assembled.body;
98
164
  try {
99
165
  if (all) {
100
166
  const fn = (p) => ns.search.companies(p);
@@ -119,14 +185,20 @@ async function runSearchCompanies(client, flags, out) {
119
185
  process.exit(1);
120
186
  }
121
187
  }
122
- async function runSearchPosts(client, flags, out) {
188
+ async function runSearchPosts(client, flags, out, readers = DEFAULT_FILTER_READERS) {
123
189
  rejectPreviewOnRead(flags.preview, out);
124
190
  const accountId = requireAccount(flags.account, out);
125
191
  const ns = client.account(accountId);
126
192
  const outOpts = resolveOutputOpts(flags);
127
193
  const all = flags.all ?? false;
128
194
  const maxPages = flags["max-pages"] ? parseInt(flags["max-pages"], 10) : 100;
129
- const body = buildSearchBody(flags);
195
+ const assembled = await buildSearchBody("posts", flags, readers);
196
+ if ("error" in assembled) {
197
+ out.stderr.write(`error: ${assembled.error}
198
+ `);
199
+ process.exit(2);
200
+ }
201
+ const body = assembled.body;
130
202
  try {
131
203
  if (all) {
132
204
  const fn = (p) => ns.search.posts(p);
@@ -151,14 +223,20 @@ async function runSearchPosts(client, flags, out) {
151
223
  process.exit(1);
152
224
  }
153
225
  }
154
- async function runSearchJobs(client, flags, out) {
226
+ async function runSearchJobs(client, flags, out, readers = DEFAULT_FILTER_READERS) {
155
227
  rejectPreviewOnRead(flags.preview, out);
156
228
  const accountId = requireAccount(flags.account, out);
157
229
  const ns = client.account(accountId);
158
230
  const outOpts = resolveOutputOpts(flags);
159
231
  const all = flags.all ?? false;
160
232
  const maxPages = flags["max-pages"] ? parseInt(flags["max-pages"], 10) : 100;
161
- const body = buildSearchBody(flags);
233
+ const assembled = await buildSearchBody("jobs", flags, readers);
234
+ if ("error" in assembled) {
235
+ out.stderr.write(`error: ${assembled.error}
236
+ `);
237
+ process.exit(2);
238
+ }
239
+ const body = assembled.body;
162
240
  try {
163
241
  if (all) {
164
242
  const fn = (p) => ns.search.jobs(p);
@@ -212,7 +290,16 @@ var searchPeopleCommand = defineCommand({
212
290
  args: {
213
291
  ...GLOBAL_FLAGS,
214
292
  keywords: { type: "string", description: "Full-text keyword search." },
215
- url: { type: "string", description: "Pasted LinkedIn search URL (mutually exclusive with filters)." }
293
+ url: { type: "string", description: "Pasted LinkedIn search URL (mutually exclusive with filters)." },
294
+ ...FILTER_FLAGS,
295
+ industry: { type: "string", description: "Industry ids (comma-separated)." },
296
+ location: { type: "string", description: "Location ids (comma-separated)." },
297
+ company: { type: "string", description: "Current company ids (comma-separated)." },
298
+ "past-company": { type: "string", description: "Past company ids (comma-separated)." },
299
+ school: { type: "string", description: "School ids (comma-separated)." },
300
+ "network-distance": { type: "string", description: "Network distance, 1-3 (comma-separated)." },
301
+ "connections-of": { type: "string", description: "Member id whose connections to search." },
302
+ "followers-of": { type: "string", description: "Member id whose followers to search." }
216
303
  },
217
304
  async run({ args }) {
218
305
  const flags = args;
@@ -236,7 +323,12 @@ var searchCompaniesCommand = defineCommand({
236
323
  meta: { name: "companies", description: "Search LinkedIn companies." },
237
324
  args: {
238
325
  ...GLOBAL_FLAGS,
239
- keywords: { type: "string", description: "Full-text keyword search." }
326
+ keywords: { type: "string", description: "Full-text keyword search." },
327
+ url: { type: "string", description: "Pasted LinkedIn search URL (mutually exclusive with filters)." },
328
+ ...FILTER_FLAGS,
329
+ industry: { type: "string", description: "Industry ids (comma-separated)." },
330
+ location: { type: "string", description: "Location ids (comma-separated)." },
331
+ "network-distance": { type: "string", description: "Network distance, 1-3 (comma-separated)." }
240
332
  },
241
333
  async run({ args }) {
242
334
  const flags = args;
@@ -260,7 +352,12 @@ var searchPostsCommand = defineCommand({
260
352
  meta: { name: "posts", description: "Search LinkedIn posts." },
261
353
  args: {
262
354
  ...GLOBAL_FLAGS,
263
- keywords: { type: "string", description: "Full-text keyword search." }
355
+ keywords: { type: "string", description: "Full-text keyword search." },
356
+ url: { type: "string", description: "Pasted LinkedIn search URL (mutually exclusive with filters)." },
357
+ ...FILTER_FLAGS,
358
+ "sort-by": { type: "string", description: "Sort order (e.g. relevance, date)." },
359
+ "date-posted": { type: "string", description: "Date-posted window (e.g. past-day, past-week)." },
360
+ "content-type": { type: "string", description: "Content type (e.g. videos, images, jobs)." }
264
361
  },
265
362
  async run({ args }) {
266
363
  const flags = args;
@@ -284,7 +381,17 @@ var searchJobsCommand = defineCommand({
284
381
  meta: { name: "jobs", description: "Search LinkedIn jobs." },
285
382
  args: {
286
383
  ...GLOBAL_FLAGS,
287
- keywords: { type: "string", description: "Full-text keyword search." }
384
+ keywords: { type: "string", description: "Full-text keyword search." },
385
+ url: { type: "string", description: "Pasted LinkedIn search URL (mutually exclusive with filters)." },
386
+ ...FILTER_FLAGS,
387
+ location: { type: "string", description: "Location ids (comma-separated)." },
388
+ industry: { type: "string", description: "Industry ids (comma-separated)." },
389
+ seniority: { type: "string", description: "Seniority ids (comma-separated)." },
390
+ function: { type: "string", description: "Job function ids (comma-separated)." },
391
+ "job-type": { type: "string", description: "Job type ids, e.g. F,P (comma-separated)." },
392
+ company: { type: "string", description: "Company ids (comma-separated)." },
393
+ "sort-by": { type: "string", description: "Sort order (e.g. relevance, recent)." },
394
+ region: { type: "string", description: "Region id." }
288
395
  },
289
396
  async run({ args }) {
290
397
  const flags = args;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curviate/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "description": "Official command-line interface for the Curviate API.",
6
6
  "license": "MIT",