@curviate/cli 0.14.0 → 0.15.1

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/README.md +52 -79
  3. package/dist/{account-7AUDAMIK.js → account-FM473HEK.js} +38 -371
  4. package/dist/{chunk-SSPILTKF.js → chunk-45KHSWCV.js} +56 -48
  5. package/dist/chunk-BGZW6B7G.js +59 -0
  6. package/dist/chunk-EEMJDJ4W.js +80 -0
  7. package/dist/{chunk-JXF47TRY.js → chunk-M33MI53G.js} +33 -1
  8. package/dist/chunk-QJZ3LWOX.js +24 -0
  9. package/dist/{chunk-4JHGVY7R.js → chunk-TMU3CSPR.js} +4 -0
  10. package/dist/{chunk-HMAGEMF7.js → chunk-ZE7QGR3F.js} +4 -0
  11. package/dist/cli.js +127 -15
  12. package/dist/comment-NCDXERSI.js +555 -0
  13. package/dist/{company-RU2CA5DY.js → company-IYTMW64G.js} +27 -74
  14. package/dist/{config-Q2AEWSEC.js → config-P5CPF5WC.js} +1 -1
  15. package/dist/{connect-QT6ZOS75.js → connect-TTJHMBUP.js} +91 -45
  16. package/dist/{exit-codes-ZTY2NY3X.js → exit-codes-SL3GQF7W.js} +1 -1
  17. package/dist/{inbox-DN7X7CM2.js → inbox-EOOGJ3ZN.js} +55 -124
  18. package/dist/job-YARGTHUY.js +631 -0
  19. package/dist/{login-BBVQLXM7.js → login-MFB45PVZ.js} +1 -1
  20. package/dist/{message-6K5E3CUF.js → message-2DLIQIE6.js} +61 -51
  21. package/dist/{post-2JQZ3OSF.js → post-GRIJ7X52.js} +188 -221
  22. package/dist/profile-57RJEL7H.js +809 -0
  23. package/dist/{recruiter-XHVMQWK6.js → recruiter-CTYH7AYG.js} +740 -226
  24. package/dist/{sales-nav-QTSTJMKA.js → sales-nav-NUMEYOEO.js} +119 -77
  25. package/dist/{search-ZGTS45BT.js → search-RD4TXNV7.js} +103 -39
  26. package/dist/{webhook-EIY5WWTE.js → webhook-CEP7SGP5.js} +8 -45
  27. package/package.json +4 -3
  28. package/dist/chunk-GXTZION6.js +0 -45
  29. package/dist/chunk-Q43HZUN3.js +0 -29
  30. package/dist/job-FGGQEXSU.js +0 -105
  31. package/dist/profile-DD5GMGNL.js +0 -501
@@ -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
+ };
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/paginate.ts
4
+ var PaginateError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.exitCode = 2;
8
+ this.name = "PaginateError";
9
+ }
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 ndjsonModeNotice() {
16
+ return "--all streams NDJSON: one object per line; the {items,cursor} envelope is not used\n";
17
+ }
18
+ var DEFAULT_PAGE_DELAY_MS = 400;
19
+ function pageDelayFrom(raw) {
20
+ if (raw === void 0 || raw === "") return void 0;
21
+ const n = Number(raw);
22
+ if (!Number.isInteger(n) || n < 0) return void 0;
23
+ return n;
24
+ }
25
+ function pageDelayFromFlags(flags) {
26
+ return pageDelayFrom(flags["page-delay"]);
27
+ }
28
+ var realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
29
+ function truncationSentinelLine(pagesFetched, hasMore) {
30
+ return JSON.stringify({ object: "stream_truncated", pages_fetched: pagesFetched, has_more: hasMore }) + "\n";
31
+ }
32
+ async function* streamAll(fn, params, opts = {}) {
33
+ const maxPages = opts.maxPages ?? 100;
34
+ const pageDelayMs = opts.pageDelayMs ?? DEFAULT_PAGE_DELAY_MS;
35
+ const sleep = opts.sleep ?? realSleep;
36
+ let cursor = void 0;
37
+ let pageCount = 0;
38
+ let firstPage = true;
39
+ while (true) {
40
+ const pageParams = cursor !== void 0 && cursor !== null ? { ...params, cursor } : params;
41
+ const page = await fn(pageParams);
42
+ pageCount++;
43
+ const items = page.items ?? page.data;
44
+ if (firstPage && !Array.isArray(items)) {
45
+ throw new PaginateError(
46
+ "--all requires a paginated method (response must have `items` or `data` array). Remove --all for non-list commands."
47
+ );
48
+ }
49
+ if (firstPage) {
50
+ if (opts.out) opts.out.stderr.write(ndjsonModeNotice());
51
+ firstPage = false;
52
+ }
53
+ if (Array.isArray(items)) {
54
+ for (const item of items) {
55
+ yield item;
56
+ }
57
+ }
58
+ cursor = page.cursor;
59
+ if (!cursor) break;
60
+ if (pageCount >= maxPages) {
61
+ const hasMore = cursor !== null && cursor !== void 0;
62
+ if (opts.out) {
63
+ opts.out.stdout.write(truncationSentinelLine(pageCount, hasMore));
64
+ opts.out.stderr.write(truncationProseNote(pageCount));
65
+ }
66
+ if (opts.onTruncated) {
67
+ opts.onTruncated(pageCount, hasMore);
68
+ }
69
+ break;
70
+ }
71
+ if (pageDelayMs > 0) await sleep(pageDelayMs);
72
+ }
73
+ }
74
+
75
+ export {
76
+ ndjsonModeNotice,
77
+ DEFAULT_PAGE_DELAY_MS,
78
+ pageDelayFromFlags,
79
+ streamAll
80
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readConfig
4
- } from "./chunk-4JHGVY7R.js";
4
+ } from "./chunk-TMU3CSPR.js";
5
5
 
6
6
  // src/lib/resolve.ts
7
7
  var DEFAULT_BASE_URL = "https://api.curviate.com";
@@ -73,10 +73,42 @@ function applyProjection(data, fields) {
73
73
  }
74
74
  return data;
75
75
  }
76
+ function firstProjectableItem(data) {
77
+ const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
78
+ if (Array.isArray(data)) {
79
+ return data.length > 0 && isPlainObject(data[0]) ? data[0] : null;
80
+ }
81
+ if (isPlainObject(data)) {
82
+ const items = data["items"];
83
+ if (Array.isArray(items)) {
84
+ return items.length > 0 && isPlainObject(items[0]) ? items[0] : null;
85
+ }
86
+ return data;
87
+ }
88
+ return null;
89
+ }
90
+ function detectUnknownFields(data, fields) {
91
+ if (fields.length === 0) return null;
92
+ const first = firstProjectableItem(data);
93
+ if (first === null) return null;
94
+ const available = Object.keys(first);
95
+ const unknown = fields.filter((f) => {
96
+ const topKey = f.split(".")[0];
97
+ return !Object.prototype.hasOwnProperty.call(first, topKey);
98
+ });
99
+ return unknown.length > 0 ? { unknown, available } : null;
100
+ }
76
101
  function renderSuccess(data, opts, out) {
77
102
  const json = isJsonMode(opts);
78
103
  const fields = opts.fields ? opts.fields.split(",").map((f) => f.trim()).filter(Boolean) : [];
79
104
  const slimmed = !opts.verbose && opts.slim ? opts.slim(data) : data;
105
+ const unknownFields = detectUnknownFields(slimmed, fields);
106
+ if (unknownFields) {
107
+ out.stderr.write(
108
+ `warning: --fields not present on the response: ${unknownFields.unknown.join(", ")}. Available keys: ${unknownFields.available.join(", ")}.
109
+ `
110
+ );
111
+ }
80
112
  const projected = applyProjection(slimmed, fields);
81
113
  if (json) {
82
114
  out.stdout.write(JSON.stringify(projected) + "\n");
@@ -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
+ };
@@ -176,6 +176,10 @@ var GLOBAL_FLAGS = {
176
176
  type: "string",
177
177
  description: "Maximum number of pages to fetch when --all is used."
178
178
  },
179
+ "page-delay": {
180
+ type: "string",
181
+ description: "Milliseconds to pause between pages when --all is used (default 400; pass 0 to disable). A modest delay keeps a long stream under the platform rate gate."
182
+ },
179
183
  preview: {
180
184
  type: "boolean",
181
185
  description: "Render the request that would be sent without calling the API.",
@@ -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
@@ -9,6 +9,47 @@ import { createRequire } from "module";
9
9
 
10
10
  // src/dispatch.ts
11
11
  import { runCommand } from "citty";
12
+ var REMOVED_COMMANDS = {
13
+ post: {
14
+ list: "`post list` was removed \u2014 use `post user-posts <user_id>` (accepts `me`).",
15
+ comment: "post comments are their own group now \u2014 use `comment add <post_id> <text>`.",
16
+ comments: "post comments are their own group now \u2014 use `comment list <post_id>`."
17
+ },
18
+ connect: {
19
+ respond: "`connect respond` was split \u2014 use `connect accept <id>` or `connect decline <id>`."
20
+ },
21
+ profile: {
22
+ connections: "`profile connections` was renamed \u2014 use `profile relations`."
23
+ },
24
+ account: {
25
+ "connect-link": "`account connect-link` was removed \u2014 use `account link [--account-id <id>]`.",
26
+ "reconnect-link": "`account reconnect-link` was removed \u2014 use `account link [--account-id <id>]`.",
27
+ reconnect: "`account reconnect` was removed \u2014 use `account link [--account-id <id>]`."
28
+ },
29
+ inbox: {
30
+ sync: "`inbox sync` was removed \u2014 history syncs automatically; just read `inbox messages <chat_id>`.",
31
+ "sync-chat": "`inbox sync-chat` was removed \u2014 history syncs automatically; just read `inbox messages <chat_id>`."
32
+ },
33
+ recruiter: {
34
+ "add-candidate": "`recruiter add-candidate` was renamed \u2014 use `recruiter save-candidate <project_id> --stage-id <id> --candidate-id <id>`.",
35
+ "project-jobs": "`recruiter project-jobs` was renamed \u2014 use `recruiter project-job get <project_id>`.",
36
+ sync: "`recruiter sync` was removed \u2014 Recruiter data syncs automatically now.",
37
+ "add-applicant": "`recruiter add-applicant` was removed with no replacement.",
38
+ "reject-applicant": "`recruiter reject-applicant` was removed with no replacement."
39
+ },
40
+ "sales-nav": {
41
+ sync: "`sales-nav sync` was removed \u2014 Sales Navigator data syncs automatically now."
42
+ },
43
+ webhook: {
44
+ "state-diff": "`webhook state-diff` was removed with no replacement."
45
+ },
46
+ company: {
47
+ followers: "`company followers` was removed with no replacement."
48
+ }
49
+ };
50
+ function successorHint(group, token) {
51
+ return REMOVED_COMMANDS[group]?.[token] ?? null;
52
+ }
12
53
  async function resolveValue(input) {
13
54
  return typeof input === "function" ? input() : input;
14
55
  }
@@ -19,6 +60,52 @@ async function nodeHasPositional(cmd) {
19
60
  const argsDef = await resolveValue(cmd.args ?? {});
20
61
  return Object.values(argsDef).some((def) => def?.type === "positional");
21
62
  }
63
+ async function nodePositionalCount(cmd) {
64
+ const argsDef = await resolveValue(cmd.args ?? {});
65
+ return Object.values(argsDef).filter((def) => def?.type === "positional").length;
66
+ }
67
+ async function booleanFlagNames(cmd) {
68
+ const names = /* @__PURE__ */ new Set();
69
+ const argsDef = await resolveValue(cmd.args ?? {});
70
+ for (const [name, def] of Object.entries(argsDef)) {
71
+ if (def?.type !== "boolean") continue;
72
+ names.add(name);
73
+ const alias = def.alias;
74
+ if (typeof alias === "string") names.add(alias);
75
+ else if (Array.isArray(alias)) for (const a of alias) names.add(a);
76
+ }
77
+ return names;
78
+ }
79
+ async function nodeName(cmd) {
80
+ const meta = await resolveValue(cmd.meta ?? {});
81
+ return meta.name ?? "this command";
82
+ }
83
+ function positionalTokens(rawArgs, booleanFlags) {
84
+ const out = [];
85
+ let afterDoubleDash = false;
86
+ for (let i = 0; i < rawArgs.length; i++) {
87
+ const arg = rawArgs[i];
88
+ if (afterDoubleDash) {
89
+ out.push({ token: arg, index: i });
90
+ continue;
91
+ }
92
+ if (arg === "--") {
93
+ afterDoubleDash = true;
94
+ continue;
95
+ }
96
+ if (arg.startsWith("-") && arg !== "-") {
97
+ const body = arg.replace(/^-+/, "");
98
+ if (body.includes("=")) continue;
99
+ const isBoolean = booleanFlags.has(body) || body.startsWith("no-") && booleanFlags.has(body.slice(3));
100
+ if (isBoolean) continue;
101
+ const next = rawArgs[i + 1];
102
+ if (next !== void 0 && !(next.startsWith("-") && next !== "-")) i++;
103
+ continue;
104
+ }
105
+ out.push({ token: arg, index: i });
106
+ }
107
+ return out;
108
+ }
22
109
  async function declaredArgNames(cmd) {
23
110
  const names = /* @__PURE__ */ new Set();
24
111
  const argsDef = await resolveValue(cmd.args ?? {});
@@ -62,8 +149,10 @@ function hasEmptyFields(rawArgs) {
62
149
  }
63
150
  return false;
64
151
  }
65
- function usageError(message) {
152
+ function usageError(message, hint) {
66
153
  process.stderr.write(`error: ${message}
154
+ `);
155
+ if (hint) process.stderr.write(`hint: ${hint}
67
156
  `);
68
157
  process.stderr.write("Run `curviate --help` for usage.\n");
69
158
  process.exit(2);
@@ -78,7 +167,29 @@ async function resolveLeaf(cmd, rawArgs) {
78
167
  const sub = await resolveValue(subCommands[token]);
79
168
  return resolveLeaf(sub, rawArgs.slice(idx + 1));
80
169
  }
170
+ if (token !== void 0) {
171
+ const hint = successorHint(await nodeName(cmd), token);
172
+ if (hint) {
173
+ usageError(`unknown command \`${token}\``, hint);
174
+ }
175
+ }
81
176
  if (token !== void 0 && hasBarePositional) {
177
+ const booleanFlags = await booleanFlagNames(cmd);
178
+ const positionals = positionalTokens(rawArgs, booleanFlags);
179
+ const declaredCount = await nodePositionalCount(cmd);
180
+ const extras = positionals.slice(declaredCount);
181
+ if (extras.length > 0) {
182
+ const first = extras[0];
183
+ if (extras.length === 1 && Object.prototype.hasOwnProperty.call(subCommands, first.token)) {
184
+ const sub = await resolveValue(subCommands[first.token]);
185
+ const remaining = rawArgs.filter((_, i) => i !== first.index);
186
+ return resolveLeaf(sub, remaining);
187
+ }
188
+ const name = await nodeName(cmd);
189
+ usageError(
190
+ `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.`
191
+ );
192
+ }
82
193
  return { leaf: cmd, leafArgs: rawArgs };
83
194
  }
84
195
  if (token !== void 0) {
@@ -139,23 +250,24 @@ var main = defineCommand({
139
250
  // Subcommand registry — names and descriptions are static for help rendering;
140
251
  // the handler implementation is loaded lazily on first invocation.
141
252
  subCommands: {
142
- login: () => import("./login-BBVQLXM7.js").then((m) => m.loginCommand),
143
- config: () => import("./config-Q2AEWSEC.js").then((m) => m.configCommand),
253
+ login: () => import("./login-MFB45PVZ.js").then((m) => m.loginCommand),
254
+ config: () => import("./config-P5CPF5WC.js").then((m) => m.configCommand),
144
255
  // ---------------------------------------------------------------------------
145
256
  // Noun groups — lazy-loaded on first invocation.
146
257
  // ---------------------------------------------------------------------------
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-EIY5WWTE.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)
258
+ profile: () => import("./profile-57RJEL7H.js").then((m) => m.profileCommand),
259
+ company: () => import("./company-IYTMW64G.js").then((m) => m.companyCommand),
260
+ job: () => import("./job-YARGTHUY.js").then((m) => m.jobCommand),
261
+ connect: () => import("./connect-TTJHMBUP.js").then((m) => m.connectCommand),
262
+ search: () => import("./search-RD4TXNV7.js").then((m) => m.searchCommand),
263
+ inbox: () => import("./inbox-EOOGJ3ZN.js").then((m) => m.inboxCommand),
264
+ message: () => import("./message-2DLIQIE6.js").then((m) => m.messageCommand),
265
+ post: () => import("./post-GRIJ7X52.js").then((m) => m.postCommand),
266
+ comment: () => import("./comment-NCDXERSI.js").then((m) => m.commentCommand),
267
+ account: () => import("./account-FM473HEK.js").then((m) => m.accountCommand),
268
+ webhook: () => import("./webhook-CEP7SGP5.js").then((m) => m.webhookCommand),
269
+ "sales-nav": () => import("./sales-nav-NUMEYOEO.js").then((m) => m.salesNavCommand),
270
+ recruiter: () => import("./recruiter-CTYH7AYG.js").then((m) => m.recruiterCommand)
159
271
  },
160
272
  async run() {
161
273
  const { runMain } = await import("citty");