@curviate/cli 0.1.0 → 0.3.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,40 @@ 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.3.0] - 2026-06-28
12
+
13
+ ### Added
14
+
15
+ - `profile me` and `profile get` now return a slim 9-field projection by default (`id`, `first_name`, `last_name`, `headline`, `location`, `industry`, `profile_url`, `picture_url`, `current_position`); pass `--verbose` to get the full response.
16
+ - `profile get` synthesizes `current_position` from `work_experience[0]` when present.
17
+ - `profile get` and `profile me` accept `--sections` to request specific LinkedIn profile sections from the API.
18
+ - `profile get --posts --is-company` resolves a company slug to an account ID automatically (non-numeric IDs call `getCompany` first).
19
+ - `company get` now returns a slim 12-field projection by default (including `headquarters` and `messaging`); pass `--verbose` to get the full response.
20
+ - `login` persists `--base-url` to the named profile; re-login without `--base-url` preserves the existing base URL.
21
+
22
+ ### Fixed
23
+
24
+ - `company` command now exits 2 with an error when `--sections` is passed (unsupported flag for that surface).
25
+ - `slimProfile` work_experience field mapping corrected (`position`→`title`, `company`→`company_name`); `is_current` now derived from `end == null`; `company_id` is always `null` (the experience-entry ID is not a company ID).
26
+
27
+ ### Changed
28
+
29
+ - Updated `@curviate/sdk` dependency to `^0.2.0` (adds `getMe` `linkedin_sections`, normalized `OwnProfile`, `Chat.subject`).
30
+
31
+ ## [0.2.0] - 2026-06-24
32
+
33
+ ### Fixed
34
+
35
+ - `message inmail` now requires and forwards the `--surface` flag (was silently dropped).
36
+ - `connect respond` now requires and forwards `--shared-secret` (was silently dropped).
37
+ - `recruiter message new` uses the correct field name `attendee_ids` (was `attendees`).
38
+ - `recruiter job create` now forwards the full job body via JSON and scalar flags (was a no-op).
39
+
40
+ ### Added
41
+
42
+ - `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.
43
+ - `search` and `recruiter search` / `sales-navigator search` accept `--url` (profile URL filter) and `--keywords`.
44
+
11
45
  ## [0.1.0] - 2026-06-22
12
46
 
13
47
  ### Added
@@ -11,10 +11,10 @@ import {
11
11
  renderSuccess,
12
12
  renderUnexpectedError,
13
13
  resolveEffectiveConfig
14
- } from "./chunk-2NCPJJPC.js";
14
+ } from "./chunk-UPNQPAJG.js";
15
15
  import {
16
16
  GLOBAL_FLAGS
17
- } from "./chunk-6JNCLLNY.js";
17
+ } from "./chunk-QQRTODHN.js";
18
18
 
19
19
  // src/commands/account.ts
20
20
  import { defineCommand } from "citty";
@@ -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
+ };
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/slim.ts
4
+ function synthesizeCurrentPosition(workExperience) {
5
+ if (!Array.isArray(workExperience) || workExperience.length === 0) {
6
+ return null;
7
+ }
8
+ const entry = workExperience[0];
9
+ return {
10
+ title: entry["position"] ?? null,
11
+ company_name: entry["company"] ?? null,
12
+ company_id: null,
13
+ is_current: entry["end"] == null
14
+ };
15
+ }
16
+ function synthesizeHeadquarters(locations) {
17
+ if (!Array.isArray(locations)) return null;
18
+ const hq = locations.find(
19
+ (l) => l["is_headquarter"] === true
20
+ );
21
+ if (!hq) return null;
22
+ return {
23
+ city: hq["city"] ?? null,
24
+ country: hq["country"] ?? null,
25
+ area: hq["area"] ?? null
26
+ };
27
+ }
28
+ function slimProfileMe(data) {
29
+ const d = data !== null && data !== void 0 && typeof data === "object" ? data : {};
30
+ const rawOrgs = Array.isArray(d["organizations"]) ? d["organizations"] : [];
31
+ const organizations = rawOrgs.map((org) => ({
32
+ id: org["id"] ?? null,
33
+ mailbox_id: org["mailbox_id"] ?? null,
34
+ name: org["name"] ?? null
35
+ }));
36
+ return {
37
+ provider_id: d["provider_id"] ?? null,
38
+ first_name: d["first_name"] ?? null,
39
+ last_name: d["last_name"] ?? null,
40
+ public_identifier: d["public_identifier"] ?? null,
41
+ location: d["location"] ?? null,
42
+ email: d["email"] ?? null,
43
+ occupation: d["occupation"] ?? null,
44
+ is_premium: d["is_premium"] ?? null,
45
+ organizations
46
+ };
47
+ }
48
+ function slimProfile(data) {
49
+ const d = data !== null && data !== void 0 && typeof data === "object" ? data : {};
50
+ const rawWE = Array.isArray(d["work_experience"]) ? d["work_experience"] : [];
51
+ const currentPosition = synthesizeCurrentPosition(rawWE);
52
+ return {
53
+ provider_id: d["provider_id"] ?? null,
54
+ first_name: d["first_name"] ?? null,
55
+ last_name: d["last_name"] ?? null,
56
+ headline: d["headline"] ?? null,
57
+ location: d["location"] ?? null,
58
+ occupation: d["occupation"] ?? null,
59
+ network_distance: d["network_distance"] ?? null,
60
+ public_identifier: d["public_identifier"] ?? null,
61
+ current_position: currentPosition
62
+ };
63
+ }
64
+ function slimCompany(data) {
65
+ const d = data !== null && data !== void 0 && typeof data === "object" ? data : {};
66
+ const rawMessaging = d["messaging"] !== null && d["messaging"] !== void 0 && typeof d["messaging"] === "object" ? d["messaging"] : null;
67
+ const messaging = {
68
+ is_enabled: rawMessaging?.["is_enabled"] ?? false
69
+ };
70
+ const rawLocations = Array.isArray(d["locations"]) ? d["locations"] : [];
71
+ const headquarters = synthesizeHeadquarters(rawLocations);
72
+ return {
73
+ id: d["id"] ?? null,
74
+ name: d["name"] ?? null,
75
+ public_identifier: d["public_identifier"] ?? null,
76
+ profile_url: d["profile_url"] ?? null,
77
+ industry: d["industry"] ?? null,
78
+ employee_count: d["employee_count"] ?? null,
79
+ employee_count_range: d["employee_count_range"] ?? null,
80
+ website: d["website"] ?? null,
81
+ foundation_date: d["foundation_date"] ?? null,
82
+ messaging,
83
+ headquarters,
84
+ followers_count: d["followers_count"] ?? null
85
+ };
86
+ }
87
+
88
+ export {
89
+ slimProfileMe,
90
+ slimProfile,
91
+ slimCompany
92
+ };
@@ -180,6 +180,11 @@ var GLOBAL_FLAGS = {
180
180
  type: "boolean",
181
181
  description: "Render the request that would be sent without calling the API.",
182
182
  default: false
183
+ },
184
+ verbose: {
185
+ type: "boolean",
186
+ description: "Output the full SDK response instead of the slim default.",
187
+ default: false
183
188
  }
184
189
  };
185
190
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readConfig
4
- } from "./chunk-6JNCLLNY.js";
4
+ } from "./chunk-QQRTODHN.js";
5
5
 
6
6
  // src/lib/resolve.ts
7
7
  var DEFAULT_BASE_URL = "https://api.curviate.com";
@@ -76,7 +76,8 @@ function applyProjection(data, fields) {
76
76
  function renderSuccess(data, opts, out) {
77
77
  const json = isJsonMode(opts);
78
78
  const fields = opts.fields ? opts.fields.split(",").map((f) => f.trim()).filter(Boolean) : [];
79
- const projected = applyProjection(data, fields);
79
+ const slimmed = !opts.verbose && opts.slim ? opts.slim(data) : data;
80
+ const projected = applyProjection(slimmed, fields);
80
81
  if (json) {
81
82
  out.stdout.write(JSON.stringify(projected) + "\n");
82
83
  } else {
package/dist/cli.js CHANGED
@@ -134,22 +134,22 @@ var main = defineCommand({
134
134
  // Subcommand registry — names and descriptions are static for help rendering;
135
135
  // the handler implementation is loaded lazily on first invocation.
136
136
  subCommands: {
137
- login: () => import("./login-VWJEBBFU.js").then((m) => m.loginCommand),
138
- config: () => import("./config-4JOXBFYX.js").then((m) => m.configCommand),
137
+ login: () => import("./login-PUTCMS5B.js").then((m) => m.loginCommand),
138
+ config: () => import("./config-E2XQNLSX.js").then((m) => m.configCommand),
139
139
  // ---------------------------------------------------------------------------
140
140
  // Noun groups — lazy-loaded on first invocation.
141
141
  // ---------------------------------------------------------------------------
142
- profile: () => import("./profile-WXA3NC7D.js").then((m) => m.profileCommand),
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),
146
- inbox: () => import("./inbox-JBLYJMV2.js").then((m) => m.inboxCommand),
147
- message: () => import("./message-IK7VGB63.js").then((m) => m.messageCommand),
148
- post: () => import("./post-EAAGVR5D.js").then((m) => m.postCommand),
149
- account: () => import("./account-YOW2MCZS.js").then((m) => m.accountCommand),
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)
142
+ profile: () => import("./profile-RU472SNL.js").then((m) => m.profileCommand),
143
+ company: () => import("./company-EARWRU2N.js").then((m) => m.companyCommand),
144
+ connect: () => import("./connect-LRYVZCN6.js").then((m) => m.connectCommand),
145
+ search: () => import("./search-ZPGKDJIT.js").then((m) => m.searchCommand),
146
+ inbox: () => import("./inbox-O4ZETUIU.js").then((m) => m.inboxCommand),
147
+ message: () => import("./message-A3XMZCQA.js").then((m) => m.messageCommand),
148
+ post: () => import("./post-HOXZPFVI.js").then((m) => m.postCommand),
149
+ account: () => import("./account-3I63RFVN.js").then((m) => m.accountCommand),
150
+ webhook: () => import("./webhook-VSM72KL5.js").then((m) => m.webhookCommand),
151
+ "sales-nav": () => import("./sales-nav-E3TH7CL7.js").then((m) => m.salesNavCommand),
152
+ recruiter: () => import("./recruiter-7SE3YCVZ.js").then((m) => m.recruiterCommand)
153
153
  },
154
154
  async run() {
155
155
  const { runMain } = await import("citty");
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ slimCompany
4
+ } from "./chunk-7TWHAKKA.js";
2
5
  import {
3
6
  resolveIdentifier
4
7
  } from "./chunk-BNUTM6KD.js";
@@ -8,10 +11,10 @@ import {
8
11
  renderSuccess,
9
12
  renderUnexpectedError,
10
13
  resolveEffectiveConfig
11
- } from "./chunk-2NCPJJPC.js";
14
+ } from "./chunk-UPNQPAJG.js";
12
15
  import {
13
16
  GLOBAL_FLAGS
14
- } from "./chunk-6JNCLLNY.js";
17
+ } from "./chunk-QQRTODHN.js";
15
18
 
16
19
  // src/commands/company.ts
17
20
  import { defineCommand } from "citty";
@@ -30,12 +33,18 @@ async function runCompanyGet(client, flags, out) {
30
33
  out.stderr.write("error: --all is not supported on non-paginated commands.\n");
31
34
  process.exit(2);
32
35
  }
36
+ if (flags.sections !== void 0) {
37
+ out.stderr.write("error: --sections is not supported on company commands.\n");
38
+ process.exit(2);
39
+ }
33
40
  const rawId = flags.id ?? "";
34
41
  const resolvedId = resolveIdentifier(rawId);
35
42
  const outOpts = {
36
43
  json: (flags.json ?? false) || !process.stdout.isTTY,
37
44
  isTTY: process.stdout.isTTY ?? false,
38
- fields: flags.fields
45
+ fields: flags.fields,
46
+ verbose: flags.verbose ?? false,
47
+ slim: slimCompany
39
48
  };
40
49
  try {
41
50
  const getCompany = flags.account ? client.account(flags.account).profiles.getCompany.bind(client.account(flags.account).profiles) : client.profiles.getCompany.bind(client.profiles);
@@ -56,7 +65,8 @@ var companyCommand = defineCommand({
56
65
  meta: { name: "company", description: "Fetch a company profile by URL or slug." },
57
66
  args: {
58
67
  ...GLOBAL_FLAGS,
59
- id: { type: "positional", description: "Company identifier (URL, slug, or native id)." }
68
+ id: { type: "positional", description: "Company identifier (URL, slug, or native id)." },
69
+ sections: { type: "string", description: "Not supported on company commands \u2014 usage error (exit 2) if supplied." }
60
70
  },
61
71
  async run({ args }) {
62
72
  const flags = args;
@@ -7,7 +7,7 @@ import {
7
7
  renameProfile,
8
8
  setActiveProfile,
9
9
  updateProfileField
10
- } from "./chunk-6JNCLLNY.js";
10
+ } from "./chunk-QQRTODHN.js";
11
11
 
12
12
  // src/commands/config.ts
13
13
  import { defineCommand } from "citty";
@@ -14,10 +14,10 @@ import {
14
14
  renderSuccess,
15
15
  renderUnexpectedError,
16
16
  resolveEffectiveConfig
17
- } from "./chunk-2NCPJJPC.js";
17
+ } from "./chunk-UPNQPAJG.js";
18
18
  import {
19
19
  GLOBAL_FLAGS
20
- } from "./chunk-6JNCLLNY.js";
20
+ } from "./chunk-QQRTODHN.js";
21
21
 
22
22
  // src/commands/connect.ts
23
23
  import { defineCommand } from "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;
@@ -8,10 +8,10 @@ import {
8
8
  renderSuccess,
9
9
  renderUnexpectedError,
10
10
  resolveEffectiveConfig
11
- } from "./chunk-2NCPJJPC.js";
11
+ } from "./chunk-UPNQPAJG.js";
12
12
  import {
13
13
  GLOBAL_FLAGS
14
- } from "./chunk-6JNCLLNY.js";
14
+ } from "./chunk-QQRTODHN.js";
15
15
 
16
16
  // src/commands/inbox.ts
17
17
  import { defineCommand } from "citty";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  GLOBAL_FLAGS,
4
4
  writeProfile
5
- } from "./chunk-6JNCLLNY.js";
5
+ } from "./chunk-QQRTODHN.js";
6
6
 
7
7
  // src/commands/login.ts
8
8
  import { defineCommand } from "citty";
@@ -54,6 +54,31 @@ async function readlineSync(prompt, opts = {}) {
54
54
  }
55
55
 
56
56
  // src/commands/login.ts
57
+ async function runLogin(args, out) {
58
+ const profileName = args.profile ?? "default";
59
+ const apiKey = (args["api-key"] ?? "").trim();
60
+ if (!apiKey) {
61
+ out.stderr.write("error: API key must not be empty.\n");
62
+ process.exit(2);
63
+ return;
64
+ }
65
+ const baseUrl = args["base-url"];
66
+ if (baseUrl === "") {
67
+ out.stderr.write("error: --base-url must not be empty.\n");
68
+ process.exit(2);
69
+ return;
70
+ }
71
+ const account = args.account;
72
+ const entry = { apiKey, account };
73
+ if (baseUrl !== void 0 && baseUrl !== "") {
74
+ entry.baseUrl = baseUrl;
75
+ }
76
+ await writeProfile(profileName, entry);
77
+ out.stderr.write(
78
+ `Saved to profile "${profileName}". Run \`curviate profile me\` to verify.
79
+ `
80
+ );
81
+ }
57
82
  var loginCommand = defineCommand({
58
83
  meta: {
59
84
  name: "login",
@@ -89,18 +114,18 @@ var loginCommand = defineCommand({
89
114
  "error: no API key \u2014 pass --api-key or run interactively on a TTY.\n"
90
115
  );
91
116
  process.exit(2);
117
+ return;
92
118
  }
93
119
  }
94
- apiKey = apiKey.trim();
95
- if (!apiKey) {
96
- process.stderr.write("error: API key must not be empty.\n");
97
- process.exit(2);
98
- }
99
- const account = args.account ?? void 0;
100
- await writeProfile(profileName, { apiKey, account });
101
- process.stderr.write(
102
- `Saved to profile "${profileName}". Run \`curviate profile me\` to verify.
103
- `
120
+ const out = { stderr: { write: (s) => process.stderr.write(s) } };
121
+ await runLogin(
122
+ {
123
+ "api-key": apiKey,
124
+ account: args.account,
125
+ "base-url": args["base-url"],
126
+ profile: profileName
127
+ },
128
+ out
104
129
  );
105
130
  }
106
131
  });
@@ -118,5 +143,6 @@ async function promptMasked(prompt) {
118
143
  return readlineSync(prompt, { mask: true });
119
144
  }
120
145
  export {
121
- loginCommand
146
+ loginCommand,
147
+ runLogin
122
148
  };
@@ -19,10 +19,10 @@ import {
19
19
  renderSuccess,
20
20
  renderUnexpectedError,
21
21
  resolveEffectiveConfig
22
- } from "./chunk-2NCPJJPC.js";
22
+ } from "./chunk-UPNQPAJG.js";
23
23
  import {
24
24
  GLOBAL_FLAGS
25
- } from "./chunk-6JNCLLNY.js";
25
+ } from "./chunk-QQRTODHN.js";
26
26
 
27
27
  // src/commands/message.ts
28
28
  import { defineCommand } from "citty";
@@ -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
  },
@@ -15,10 +15,10 @@ import {
15
15
  renderSuccess,
16
16
  renderUnexpectedError,
17
17
  resolveEffectiveConfig
18
- } from "./chunk-2NCPJJPC.js";
18
+ } from "./chunk-UPNQPAJG.js";
19
19
  import {
20
20
  GLOBAL_FLAGS
21
- } from "./chunk-6JNCLLNY.js";
21
+ } from "./chunk-QQRTODHN.js";
22
22
 
23
23
  // src/commands/post.ts
24
24
  import { defineCommand } from "citty";
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ slimProfile,
4
+ slimProfileMe
5
+ } from "./chunk-7TWHAKKA.js";
2
6
  import {
3
7
  buildPreviewOutput
4
8
  } from "./chunk-R3VLWLVV.js";
@@ -14,10 +18,10 @@ import {
14
18
  renderSuccess,
15
19
  renderUnexpectedError,
16
20
  resolveEffectiveConfig
17
- } from "./chunk-2NCPJJPC.js";
21
+ } from "./chunk-UPNQPAJG.js";
18
22
  import {
19
23
  GLOBAL_FLAGS
20
- } from "./chunk-6JNCLLNY.js";
24
+ } from "./chunk-QQRTODHN.js";
21
25
 
22
26
  // src/commands/profile.ts
23
27
  import { defineCommand } from "citty";
@@ -50,18 +54,28 @@ function resolveOutputOpts(flags) {
50
54
  return {
51
55
  json: (flags.json ?? false) || !process.stdout.isTTY,
52
56
  isTTY: process.stdout.isTTY ?? false,
53
- fields: flags.fields
57
+ fields: flags.fields,
58
+ verbose: flags.verbose ?? false
54
59
  };
55
60
  }
56
61
  async function runProfileMe(client, flags, out) {
57
62
  rejectPreviewOnRead(flags.preview, out);
58
63
  rejectAllOnNonPaginated(flags.all, out);
64
+ if (flags.sections === "") {
65
+ out.stderr.write("error: --sections must not be empty. Omit the flag or provide section names.\n");
66
+ process.exit(2);
67
+ return;
68
+ }
59
69
  const accountId = requireAccount(flags.account, out);
60
70
  const ns = client.account(accountId);
71
+ const params = {};
72
+ if (flags.sections) {
73
+ params["linkedin_sections"] = flags.sections.split(",").map((s) => s.trim()).filter(Boolean);
74
+ }
61
75
  try {
62
- const result = await ns.profiles.getMe();
63
- const opts = resolveOutputOpts(flags);
64
- renderSuccess(result, opts, out);
76
+ const result = await ns.profiles.getMe(params);
77
+ const outOpts = { ...resolveOutputOpts(flags), slim: slimProfileMe };
78
+ renderSuccess(result, outOpts, out);
65
79
  } catch (err) {
66
80
  const { CurviateError } = await import("@curviate/sdk");
67
81
  if (err instanceof CurviateError) {
@@ -75,6 +89,12 @@ async function runProfileMe(client, flags, out) {
75
89
  }
76
90
  async function runProfileGet(client, flags, out) {
77
91
  rejectPreviewOnRead(flags.preview, out);
92
+ const isListCommand = flags.posts || flags.comments || flags.reactions || flags.followers;
93
+ if (!isListCommand && flags.sections === "") {
94
+ out.stderr.write("error: --sections must not be empty. Omit the flag or provide section names.\n");
95
+ process.exit(2);
96
+ return;
97
+ }
78
98
  const accountId = requireAccount(flags.account, out);
79
99
  const rawId = flags.id ?? "";
80
100
  const resolvedId = resolveIdentifier(rawId);
@@ -90,8 +110,16 @@ async function runProfileGet(client, flags, out) {
90
110
  if (flags["is-company"]) params["is_company"] = true;
91
111
  if (limit !== void 0) params["limit"] = limit;
92
112
  if (cursor) params["cursor"] = cursor;
113
+ let postId = resolvedId;
114
+ if (flags["is-company"]) {
115
+ const isNumericId = /^\d+$/.test(resolvedId);
116
+ if (!isNumericId) {
117
+ const companyData = await ns.profiles.getCompany(resolvedId);
118
+ postId = companyData["id"];
119
+ }
120
+ }
93
121
  if (all) {
94
- const fn = (p) => ns.profiles.listPosts(resolvedId, p);
122
+ const fn = (p) => ns.profiles.listPosts(postId, p);
95
123
  for await (const item of streamAll(fn, params, {
96
124
  maxPages,
97
125
  onTruncated: (msg) => out.stderr.write(msg + "\n")
@@ -99,7 +127,7 @@ async function runProfileGet(client, flags, out) {
99
127
  out.stdout.write(JSON.stringify(item) + "\n");
100
128
  }
101
129
  } else {
102
- const result = await ns.profiles.listPosts(resolvedId, params);
130
+ const result = await ns.profiles.listPosts(postId, params);
103
131
  renderSuccess(result, outOpts, out);
104
132
  }
105
133
  } else if (flags.comments) {
@@ -154,8 +182,12 @@ async function runProfileGet(client, flags, out) {
154
182
  rejectAllOnNonPaginated(flags.all, out);
155
183
  const params = {};
156
184
  if (flags.notify) params["notify"] = true;
185
+ if (flags.sections) {
186
+ params["linkedin_sections"] = flags.sections.split(",").map((s) => s.trim()).filter(Boolean);
187
+ }
157
188
  const result = await ns.profiles.get(resolvedId, params);
158
- renderSuccess(result, outOpts, out);
189
+ const getOutOpts = { ...outOpts, slim: slimProfile };
190
+ renderSuccess(result, getOutOpts, out);
159
191
  }
160
192
  } catch (err) {
161
193
  const { CurviateError } = await import("@curviate/sdk");
@@ -237,7 +269,13 @@ async function runProfileEndorse(client, flags, out) {
237
269
  }
238
270
  var profileMeCommand = defineCommand({
239
271
  meta: { name: "me", description: "Get your own LinkedIn profile." },
240
- args: { ...GLOBAL_FLAGS },
272
+ args: {
273
+ ...GLOBAL_FLAGS,
274
+ sections: {
275
+ type: "string",
276
+ description: "Comma-separated LinkedIn sections to fetch (e.g. experience,education)."
277
+ }
278
+ },
241
279
  async run({ args }) {
242
280
  const flags = args;
243
281
  const cfg = await resolveEffectiveConfig({
@@ -312,7 +350,11 @@ var profileCommand = defineCommand({
312
350
  reactions: { type: "boolean", description: "List the profile's reactions.", default: false },
313
351
  followers: { type: "boolean", description: "List the profile's followers.", default: false },
314
352
  "is-company": { type: "boolean", description: "When listing posts, treat the profile as a company page.", default: false },
315
- notify: { type: "boolean", description: "Signal a profile view when fetching.", default: false }
353
+ notify: { type: "boolean", description: "Signal a profile view when fetching.", default: false },
354
+ sections: {
355
+ type: "string",
356
+ description: "Comma-separated LinkedIn sections to fetch (e.g. experience,education)."
357
+ }
316
358
  },
317
359
  subCommands: {
318
360
  me: profileMeCommand,
@@ -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";
@@ -22,13 +27,14 @@ import {
22
27
  renderSuccess,
23
28
  renderUnexpectedError,
24
29
  resolveEffectiveConfig
25
- } from "./chunk-2NCPJJPC.js";
30
+ } from "./chunk-UPNQPAJG.js";
26
31
  import {
27
32
  GLOBAL_FLAGS
28
- } from "./chunk-6JNCLLNY.js";
33
+ } from "./chunk-QQRTODHN.js";
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";
@@ -18,10 +24,10 @@ import {
18
24
  renderSuccess,
19
25
  renderUnexpectedError,
20
26
  resolveEffectiveConfig
21
- } from "./chunk-2NCPJJPC.js";
27
+ } from "./chunk-UPNQPAJG.js";
22
28
  import {
23
29
  GLOBAL_FLAGS
24
- } from "./chunk-6JNCLLNY.js";
30
+ } from "./chunk-QQRTODHN.js";
25
31
 
26
32
  // src/commands/sales-nav.ts
27
33
  import { defineCommand } from "citty";
@@ -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";
@@ -8,13 +14,23 @@ import {
8
14
  renderSuccess,
9
15
  renderUnexpectedError,
10
16
  resolveEffectiveConfig
11
- } from "./chunk-2NCPJJPC.js";
17
+ } from "./chunk-UPNQPAJG.js";
12
18
  import {
13
19
  GLOBAL_FLAGS
14
- } from "./chunk-6JNCLLNY.js";
20
+ } from "./chunk-QQRTODHN.js";
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;
@@ -11,10 +11,10 @@ import {
11
11
  renderSuccess,
12
12
  renderUnexpectedError,
13
13
  resolveEffectiveConfig
14
- } from "./chunk-2NCPJJPC.js";
14
+ } from "./chunk-UPNQPAJG.js";
15
15
  import {
16
16
  GLOBAL_FLAGS
17
- } from "./chunk-6JNCLLNY.js";
17
+ } from "./chunk-QQRTODHN.js";
18
18
 
19
19
  // src/commands/webhook.ts
20
20
  import { defineCommand } from "citty";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curviate/cli",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "Official command-line interface for the Curviate API.",
6
6
  "license": "MIT",
@@ -17,7 +17,7 @@
17
17
  "access": "public"
18
18
  },
19
19
  "bin": {
20
- "curviate": "./dist/cli.js"
20
+ "curviate": "dist/cli.js"
21
21
  },
22
22
  "files": [
23
23
  "dist/",
@@ -40,7 +40,7 @@
40
40
  "clean": "rm -rf dist *.tsbuildinfo"
41
41
  },
42
42
  "dependencies": {
43
- "@curviate/sdk": "^0.1.1",
43
+ "@curviate/sdk": "^0.2.0",
44
44
  "citty": "^0.1.6"
45
45
  },
46
46
  "devDependencies": {