@curviate/cli 0.13.0 → 0.15.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.
@@ -21,59 +21,65 @@ function synthesizeHeadquarters(locations) {
21
21
  if (!hq) return null;
22
22
  return {
23
23
  city: hq["city"] ?? null,
24
- country: hq["country"] ?? null,
24
+ country_code: hq["country_code"] ?? null,
25
+ postal_code: hq["postal_code"] ?? null,
25
26
  area: hq["area"] ?? null
26
27
  };
27
28
  }
29
+ function getSpecifics(d) {
30
+ return d["specifics"] !== null && d["specifics"] !== void 0 && typeof d["specifics"] === "object" ? d["specifics"] : null;
31
+ }
32
+ function extractExperience(specifics) {
33
+ return Array.isArray(specifics?.["experience"]) ? specifics["experience"] : [];
34
+ }
28
35
  function slimProfileMe(data) {
29
36
  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
- const rawWE = Array.isArray(d["work_experience"]) ? d["work_experience"] : [];
37
- const currentPosition = synthesizeCurrentPosition(rawWE);
37
+ const specifics = getSpecifics(d);
38
+ const currentPosition = synthesizeCurrentPosition(extractExperience(specifics));
38
39
  return {
39
- provider_id: d["provider_id"] ?? null,
40
+ provider_id: d["id"] ?? null,
40
41
  first_name: d["first_name"] ?? null,
41
42
  last_name: d["last_name"] ?? null,
43
+ // LinkedIn serves the profile headline in the `description` wire field
44
+ // on reads, not a field named `headline` — see JSDoc above.
45
+ headline: d["description"] ?? null,
42
46
  public_identifier: d["public_identifier"] ?? null,
43
47
  location: d["location"] ?? null,
44
- email: d["email"] ?? null,
45
- occupation: d["occupation"] ?? null,
46
- is_premium: d["is_premium"] ?? null,
47
- organizations,
48
+ emails: Array.isArray(d["emails"]) ? d["emails"] : [],
49
+ is_premium: specifics?.["is_premium"] ?? null,
48
50
  current_position: currentPosition
49
51
  };
50
52
  }
51
53
  function slimProfile(data) {
52
54
  const d = data !== null && data !== void 0 && typeof data === "object" ? data : {};
53
- const rawWE = Array.isArray(d["work_experience"]) ? d["work_experience"] : [];
54
- const currentPosition = synthesizeCurrentPosition(rawWE);
55
+ const specifics = getSpecifics(d);
56
+ const currentPosition = synthesizeCurrentPosition(extractExperience(specifics));
55
57
  return {
56
- provider_id: d["provider_id"] ?? null,
58
+ provider_id: d["id"] ?? null,
57
59
  first_name: d["first_name"] ?? null,
58
60
  last_name: d["last_name"] ?? null,
59
- headline: d["headline"] ?? null,
61
+ // LinkedIn serves the profile headline in the `description` wire field
62
+ // on reads, not a field named `headline` — see slimProfileMe's JSDoc.
63
+ headline: d["description"] ?? null,
60
64
  location: d["location"] ?? null,
61
- occupation: d["occupation"] ?? null,
62
- network_distance: d["network_distance"] ?? null,
65
+ network_distance: specifics?.["network_distance"] ?? null,
63
66
  public_identifier: d["public_identifier"] ?? null,
64
67
  current_position: currentPosition
65
68
  };
66
69
  }
67
70
  function slimInviteSentItem(item) {
71
+ const rawUser = item["user"] !== null && item["user"] !== void 0 && typeof item["user"] === "object" ? item["user"] : null;
72
+ const user = rawUser !== null ? {
73
+ id: rawUser["id"] ?? null,
74
+ display_name: rawUser["display_name"] ?? null,
75
+ first_name: rawUser["first_name"] ?? null,
76
+ last_name: rawUser["last_name"] ?? null
77
+ } : null;
68
78
  return {
69
79
  id: item["id"] ?? null,
70
- invited_user: item["invited_user"] ?? null,
71
- invited_user_id: item["invited_user_id"] ?? null,
72
- invited_user_public_id: item["invited_user_public_id"] ?? null,
73
- invited_user_description: item["invited_user_description"] ?? null,
74
- date: item["date"] ?? null,
75
- parsed_datetime: item["parsed_datetime"] ?? null,
76
- invitation_text: item["invitation_text"] ?? null
80
+ created_at: item["created_at"] ?? null,
81
+ message: item["message"] ?? null,
82
+ user
77
83
  };
78
84
  }
79
85
  function slimInviteSent(data) {
@@ -86,15 +92,18 @@ function slimInviteSent(data) {
86
92
  };
87
93
  }
88
94
  function slimInviteReceivedItem(item) {
89
- const rawSpecifics = item["specifics"] !== null && item["specifics"] !== void 0 && typeof item["specifics"] === "object" ? item["specifics"] : null;
90
- const specifics = rawSpecifics !== null ? { shared_secret: rawSpecifics["shared_secret"] ?? null } : null;
95
+ const rawUser = item["user"] !== null && item["user"] !== void 0 && typeof item["user"] === "object" ? item["user"] : null;
96
+ const user = rawUser !== null ? {
97
+ id: rawUser["id"] ?? null,
98
+ display_name: rawUser["display_name"] ?? null,
99
+ first_name: rawUser["first_name"] ?? null,
100
+ last_name: rawUser["last_name"] ?? null,
101
+ public_identifier: rawUser["public_identifier"] ?? null
102
+ } : null;
91
103
  return {
92
104
  id: item["id"] ?? null,
93
- inviter: item["inviter"] ?? null,
94
- date: item["date"] ?? null,
95
- parsed_datetime: item["parsed_datetime"] ?? null,
96
- invitation_text: item["invitation_text"] ?? null,
97
- specifics
105
+ created_at: item["created_at"] ?? null,
106
+ user
98
107
  };
99
108
  }
100
109
  function slimInviteReceived(data) {
@@ -166,8 +175,7 @@ function slimSearchPostsItem(item) {
166
175
  text = s.length > 200 ? s.slice(0, 200) : s;
167
176
  }
168
177
  return {
169
- post_urn: item["post_urn"] ?? null,
170
- posted_at: item["posted_at"] ?? null,
178
+ id: item["id"] ?? null,
171
179
  author,
172
180
  text,
173
181
  reaction_count: item["reaction_count"] ?? null,
@@ -211,25 +219,26 @@ function slimAccountGet(data) {
211
219
  }
212
220
  function slimJob(data) {
213
221
  const d = data !== null && data !== void 0 && typeof data === "object" ? data : {};
222
+ const rawCompany = d["company"] !== null && d["company"] !== void 0 && typeof d["company"] === "object" ? d["company"] : null;
223
+ const companyId = rawCompany?.["id"] ?? null;
214
224
  return {
215
225
  object: d["object"] ?? null,
216
226
  id: d["id"] ?? null,
217
227
  title: d["title"] ?? null,
218
228
  company: d["company"] ?? null,
219
- company_id: d["company_id"] ?? null,
229
+ company_id: companyId,
220
230
  location: d["location"] ?? null,
221
231
  state: d["state"] ?? null,
222
- applicants_counter: d["applicants_counter"] ?? null,
223
- published_at: d["published_at"] ?? null,
232
+ applications_count: d["applications_count"] ?? null,
233
+ published_at: d["published_at"] ?? d["created_at"] ?? null,
224
234
  description: d["description"] ?? null
225
235
  };
226
236
  }
227
237
  function slimCompany(data) {
228
238
  const d = data !== null && data !== void 0 && typeof data === "object" ? data : {};
229
- const rawMessaging = d["messaging"] !== null && d["messaging"] !== void 0 && typeof d["messaging"] === "object" ? d["messaging"] : null;
230
- const messaging = {
231
- is_enabled: rawMessaging?.["is_enabled"] ?? false
232
- };
239
+ const rawInsights = d["insights"] !== null && d["insights"] !== void 0 && typeof d["insights"] === "object" ? d["insights"] : null;
240
+ const rawHeadcountRange = rawInsights?.["headcount_range"] !== null && rawInsights?.["headcount_range"] !== void 0 && typeof rawInsights?.["headcount_range"] === "object" ? rawInsights["headcount_range"] : null;
241
+ const employeeCountRange = rawHeadcountRange !== null ? { from: rawHeadcountRange["from"] ?? null } : null;
233
242
  const rawLocations = Array.isArray(d["locations"]) ? d["locations"] : [];
234
243
  const headquarters = synthesizeHeadquarters(rawLocations);
235
244
  return {
@@ -238,13 +247,12 @@ function slimCompany(data) {
238
247
  public_identifier: d["public_identifier"] ?? null,
239
248
  profile_url: d["profile_url"] ?? null,
240
249
  industry: d["industry"] ?? null,
241
- employee_count: d["employee_count"] ?? null,
242
- employee_count_range: d["employee_count_range"] ?? null,
250
+ employee_count: rawInsights?.["headcount"] ?? null,
251
+ employee_count_range: employeeCountRange,
243
252
  website: d["website"] ?? null,
244
- foundation_date: d["foundation_date"] ?? null,
245
- messaging,
253
+ establishment_year: d["establishment_year"] ?? null,
246
254
  headquarters,
247
- followers_count: d["followers_count"] ?? null
255
+ follower_count: d["follower_count"] ?? null
248
256
  };
249
257
  }
250
258
 
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/attach.ts
4
+ import { readFile } from "fs/promises";
5
+ import { basename, extname } from "path";
6
+ var AttachError = class extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.exitCode = 2;
10
+ this.name = "AttachError";
11
+ }
12
+ };
13
+ async function readAttachment(filePath) {
14
+ try {
15
+ const buf = await readFile(filePath);
16
+ return buf;
17
+ } catch (err) {
18
+ const filename = basename(filePath);
19
+ const reason = err instanceof Error ? err.message : "unknown error";
20
+ throw new AttachError(
21
+ `Cannot read attachment "${filename}": ${reason}. Pass a valid file path.`
22
+ );
23
+ }
24
+ }
25
+ function describeAttachment(filePath, buf) {
26
+ return `${basename(filePath)} (${buf.byteLength} bytes)`;
27
+ }
28
+ var MIME_BY_EXT = {
29
+ ".jpg": "image/jpeg",
30
+ ".jpeg": "image/jpeg",
31
+ ".png": "image/png",
32
+ ".gif": "image/gif",
33
+ ".webp": "image/webp",
34
+ ".pdf": "application/pdf",
35
+ ".mp4": "video/mp4",
36
+ ".mov": "video/quicktime",
37
+ ".webm": "video/webm",
38
+ ".mp3": "audio/mpeg",
39
+ ".m4a": "audio/mp4",
40
+ ".wav": "audio/wav",
41
+ ".ogg": "audio/ogg"
42
+ };
43
+ function guessContentType(filePath) {
44
+ return MIME_BY_EXT[extname(filePath).toLowerCase()] ?? "application/octet-stream";
45
+ }
46
+ function toAttachmentPayload(filePath, buf) {
47
+ return {
48
+ content: buf.toString("base64"),
49
+ content_type: guessContentType(filePath),
50
+ filename: basename(filePath)
51
+ };
52
+ }
53
+
54
+ export {
55
+ AttachError,
56
+ readAttachment,
57
+ describeAttachment,
58
+ toAttachmentPayload
59
+ };
@@ -8,6 +8,13 @@ var PaginateError = class extends Error {
8
8
  this.name = "PaginateError";
9
9
  }
10
10
  };
11
+ function truncationProseNote(pagesFetched) {
12
+ return `Streaming truncated at ${pagesFetched} page(s). Use --all --max-pages or --cursor for manual paging.
13
+ `;
14
+ }
15
+ function truncationSentinelLine(pagesFetched, hasMore) {
16
+ return JSON.stringify({ object: "stream_truncated", pages_fetched: pagesFetched, has_more: hasMore }) + "\n";
17
+ }
11
18
  async function* streamAll(fn, params, opts = {}) {
12
19
  const maxPages = opts.maxPages ?? 100;
13
20
  let cursor = void 0;
@@ -32,8 +39,13 @@ async function* streamAll(fn, params, opts = {}) {
32
39
  cursor = page.cursor;
33
40
  if (!cursor) break;
34
41
  if (pageCount >= maxPages) {
42
+ const hasMore = cursor !== null && cursor !== void 0;
43
+ if (opts.out) {
44
+ opts.out.stdout.write(truncationSentinelLine(pageCount, hasMore));
45
+ opts.out.stderr.write(truncationProseNote(pageCount));
46
+ }
35
47
  if (opts.onTruncated) {
36
- opts.onTruncated(pageCount, cursor !== null && cursor !== void 0);
48
+ opts.onTruncated(pageCount, hasMore);
37
49
  }
38
50
  break;
39
51
  }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveIdentifier
4
+ } from "./chunk-DMQZEPQE.js";
5
+
6
+ // src/lib/member-id.ts
7
+ var MEMBER_PROVIDER_ID_RE = /^A[CDE][A-Za-z0-9_-]{4,}$/;
8
+ async function resolveMemberProviderId(ns, raw) {
9
+ const normalized = resolveIdentifier(raw);
10
+ if (MEMBER_PROVIDER_ID_RE.test(normalized)) return normalized;
11
+ const profile = await ns.users.get(normalized, {});
12
+ return profile.id;
13
+ }
14
+ async function resolveMemberOrMeProviderId(ns, raw) {
15
+ const normalized = resolveIdentifier(raw);
16
+ if (normalized === "me" || MEMBER_PROVIDER_ID_RE.test(normalized)) return normalized;
17
+ const profile = await ns.users.get(normalized, {});
18
+ return profile.id;
19
+ }
20
+
21
+ export {
22
+ resolveMemberProviderId,
23
+ resolveMemberOrMeProviderId
24
+ };
@@ -21,6 +21,7 @@ var EXIT_CODE_MAP = {
21
21
  RATE_LIMIT_TENANT: 6,
22
22
  PLATFORM_RATE_LIMIT: 6,
23
23
  LINKEDIN_RATE_LIMITED: 6,
24
+ RATE_LIMITED: 6,
24
25
  // Transient platform (7)
25
26
  PLATFORM_ERROR: 7,
26
27
  LINKEDIN_SERVICE_UNAVAILABLE: 7,
@@ -30,6 +31,9 @@ var EXIT_CODE_MAP = {
30
31
  LINKEDIN_AUTH_FAILED: 8,
31
32
  LINKEDIN_COOKIE_INVALID: 8,
32
33
  CONNECTION_IN_PROGRESS: 8,
34
+ ACCOUNT_ALREADY_LINKED: 8,
35
+ LINKEDIN_OPERATION_NOT_SUPPORTED: 8,
36
+ CONNECTION_REQUEST_CONFLICT: 8,
33
37
  // Checkpoint flow (9)
34
38
  CHECKPOINT_NOT_FOUND: 9,
35
39
  CHECKPOINT_EXPIRED: 9,
package/dist/cli.js CHANGED
@@ -19,6 +19,52 @@ async function nodeHasPositional(cmd) {
19
19
  const argsDef = await resolveValue(cmd.args ?? {});
20
20
  return Object.values(argsDef).some((def) => def?.type === "positional");
21
21
  }
22
+ async function nodePositionalCount(cmd) {
23
+ const argsDef = await resolveValue(cmd.args ?? {});
24
+ return Object.values(argsDef).filter((def) => def?.type === "positional").length;
25
+ }
26
+ async function booleanFlagNames(cmd) {
27
+ const names = /* @__PURE__ */ new Set();
28
+ const argsDef = await resolveValue(cmd.args ?? {});
29
+ for (const [name, def] of Object.entries(argsDef)) {
30
+ if (def?.type !== "boolean") continue;
31
+ names.add(name);
32
+ const alias = def.alias;
33
+ if (typeof alias === "string") names.add(alias);
34
+ else if (Array.isArray(alias)) for (const a of alias) names.add(a);
35
+ }
36
+ return names;
37
+ }
38
+ async function nodeName(cmd) {
39
+ const meta = await resolveValue(cmd.meta ?? {});
40
+ return meta.name ?? "this command";
41
+ }
42
+ function positionalTokens(rawArgs, booleanFlags) {
43
+ const out = [];
44
+ let afterDoubleDash = false;
45
+ for (let i = 0; i < rawArgs.length; i++) {
46
+ const arg = rawArgs[i];
47
+ if (afterDoubleDash) {
48
+ out.push({ token: arg, index: i });
49
+ continue;
50
+ }
51
+ if (arg === "--") {
52
+ afterDoubleDash = true;
53
+ continue;
54
+ }
55
+ if (arg.startsWith("-") && arg !== "-") {
56
+ const body = arg.replace(/^-+/, "");
57
+ if (body.includes("=")) continue;
58
+ const isBoolean = booleanFlags.has(body) || body.startsWith("no-") && booleanFlags.has(body.slice(3));
59
+ if (isBoolean) continue;
60
+ const next = rawArgs[i + 1];
61
+ if (next !== void 0 && !(next.startsWith("-") && next !== "-")) i++;
62
+ continue;
63
+ }
64
+ out.push({ token: arg, index: i });
65
+ }
66
+ return out;
67
+ }
22
68
  async function declaredArgNames(cmd) {
23
69
  const names = /* @__PURE__ */ new Set();
24
70
  const argsDef = await resolveValue(cmd.args ?? {});
@@ -79,6 +125,22 @@ async function resolveLeaf(cmd, rawArgs) {
79
125
  return resolveLeaf(sub, rawArgs.slice(idx + 1));
80
126
  }
81
127
  if (token !== void 0 && hasBarePositional) {
128
+ const booleanFlags = await booleanFlagNames(cmd);
129
+ const positionals = positionalTokens(rawArgs, booleanFlags);
130
+ const declaredCount = await nodePositionalCount(cmd);
131
+ const extras = positionals.slice(declaredCount);
132
+ if (extras.length > 0) {
133
+ const first = extras[0];
134
+ if (extras.length === 1 && Object.prototype.hasOwnProperty.call(subCommands, first.token)) {
135
+ const sub = await resolveValue(subCommands[first.token]);
136
+ const remaining = rawArgs.filter((_, i) => i !== first.index);
137
+ return resolveLeaf(sub, remaining);
138
+ }
139
+ const name = await nodeName(cmd);
140
+ usageError(
141
+ `unexpected argument \`${first.token}\` after \`${name}\`. It is neither a positional \`${name}\` accepts nor one of its subcommands. Run \`curviate ${name} --help\` for the available subcommands.`
142
+ );
143
+ }
82
144
  return { leaf: cmd, leafArgs: rawArgs };
83
145
  }
84
146
  if (token !== void 0) {
@@ -144,18 +206,19 @@ var main = defineCommand({
144
206
  // ---------------------------------------------------------------------------
145
207
  // Noun groups — lazy-loaded on first invocation.
146
208
  // ---------------------------------------------------------------------------
147
- profile: () => import("./profile-DD5GMGNL.js").then((m) => m.profileCommand),
148
- company: () => import("./company-RU2CA5DY.js").then((m) => m.companyCommand),
149
- job: () => import("./job-FGGQEXSU.js").then((m) => m.jobCommand),
150
- connect: () => import("./connect-QT6ZOS75.js").then((m) => m.connectCommand),
151
- search: () => import("./search-ZGTS45BT.js").then((m) => m.searchCommand),
152
- inbox: () => import("./inbox-DN7X7CM2.js").then((m) => m.inboxCommand),
153
- message: () => import("./message-6K5E3CUF.js").then((m) => m.messageCommand),
154
- post: () => import("./post-2JQZ3OSF.js").then((m) => m.postCommand),
155
- account: () => import("./account-7AUDAMIK.js").then((m) => m.accountCommand),
156
- webhook: () => import("./webhook-XPWBI675.js").then((m) => m.webhookCommand),
157
- "sales-nav": () => import("./sales-nav-QTSTJMKA.js").then((m) => m.salesNavCommand),
158
- recruiter: () => import("./recruiter-XHVMQWK6.js").then((m) => m.recruiterCommand)
209
+ profile: () => import("./profile-I6YMVXSS.js").then((m) => m.profileCommand),
210
+ company: () => import("./company-LGID3SK3.js").then((m) => m.companyCommand),
211
+ job: () => import("./job-TFTTCP5B.js").then((m) => m.jobCommand),
212
+ connect: () => import("./connect-A7HEKKEE.js").then((m) => m.connectCommand),
213
+ search: () => import("./search-6C72M563.js").then((m) => m.searchCommand),
214
+ inbox: () => import("./inbox-44JRTJAP.js").then((m) => m.inboxCommand),
215
+ message: () => import("./message-FOJTDPW3.js").then((m) => m.messageCommand),
216
+ post: () => import("./post-FC6NZXM5.js").then((m) => m.postCommand),
217
+ comment: () => import("./comment-Y5IU42CR.js").then((m) => m.commentCommand),
218
+ account: () => import("./account-VB52GIUA.js").then((m) => m.accountCommand),
219
+ webhook: () => import("./webhook-NSXEIU44.js").then((m) => m.webhookCommand),
220
+ "sales-nav": () => import("./sales-nav-6IYKQ4TC.js").then((m) => m.salesNavCommand),
221
+ recruiter: () => import("./recruiter-BHT5OJD7.js").then((m) => m.recruiterCommand)
159
222
  },
160
223
  async run() {
161
224
  const { runMain } = await import("citty");