@curviate/cli 0.19.0 → 0.20.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.
Files changed (26) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/dist/{account-EGJJNBXW.js → account-2SOSCEB6.js} +26 -26
  3. package/dist/{chunk-TMU3CSPR.js → chunk-DW2HCRXJ.js} +1 -1
  4. package/dist/{chunk-M33MI53G.js → chunk-HDQUEFD2.js} +1 -1
  5. package/dist/chunk-HOQ7EUHJ.js +50 -0
  6. package/dist/{chunk-KVV6LZ7E.js → chunk-IGCQNDIG.js} +53 -17
  7. package/dist/cli.js +33 -33
  8. package/dist/{comment-VT6PO4MN.js → comment-AXDINBPS.js} +3 -3
  9. package/dist/{company-XEEBBCRZ.js → company-BX4VDXZX.js} +19 -18
  10. package/dist/{config-P5CPF5WC.js → config-3O4N2XSH.js} +2 -2
  11. package/dist/{connect-6PU7QCOL.js → connect-KWOJKCIB.js} +36 -15
  12. package/dist/{feed-KAUDRE5K.js → feed-4ROKUBG5.js} +3 -3
  13. package/dist/{group-LPVVJ2JX.js → group-L7TQEEFN.js} +7 -7
  14. package/dist/{inbox-BXCWHGWP.js → inbox-GMGZVDTQ.js} +9 -9
  15. package/dist/{inboxes-MTPWLP5F.js → inboxes-YVYLSM5W.js} +4 -4
  16. package/dist/{job-EIXBJXLW.js → job-TYBA2DUQ.js} +7 -7
  17. package/dist/{login-GDLWR542.js → login-W7NCDZOQ.js} +2 -2
  18. package/dist/{message-UWMRE2SJ.js → message-C6FV6JH6.js} +6 -3
  19. package/dist/{notification-RYFDHM3G.js → notification-45XFT3S4.js} +5 -5
  20. package/dist/{post-HK6HHIKV.js → post-RETV3WUG.js} +8 -8
  21. package/dist/{profile-PES67647.js → profile-A2TWF4HB.js} +13 -13
  22. package/dist/{recruiter-MVPSTH2V.js → recruiter-FQAKY6Z2.js} +30 -30
  23. package/dist/{sales-nav-TMXWDKSE.js → sales-nav-SSJIZUNB.js} +16 -16
  24. package/dist/{search-O2LACGHE.js → search-DH6BJFQC.js} +13 -13
  25. package/dist/{webhook-QFSZN3P2.js → webhook-LED6AKF3.js} +50 -19
  26. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -8,6 +8,101 @@ 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.20.0] - 2026-08-03
12
+
13
+ A safety and correctness release. Upgrade promptly: it closes a path by which a
14
+ mistyped command sent a message, and it is the first release in which
15
+ `curviate webhook verify` works at all.
16
+
17
+ ### Fixed
18
+
19
+ - **An unknown subcommand under `message` no longer becomes a send.**
20
+ `curviate message search "sophie"` did not error. `message` accepts both
21
+ subcommands and a bare `message <chat_id> "<text>"` form, and an unregistered
22
+ first token fell through to the bare form, so it bound `chatId="search"`,
23
+ `text="sophie"` and **sent a message**. That applied to any unknown or
24
+ mistyped subcommand: with `search` it 404s, but with a real chat id in that
25
+ position, or a paste that lands one there, it messaged a real person.
26
+
27
+ An unknown subcommand now exits 2 with usage and issues **no request at all**.
28
+ The legitimate `curviate message <chat_id> "<text>"` form is unchanged, and
29
+ `curviate message send <chat_id> "<text>"` is always available for a chat id
30
+ the check does not recognise.
31
+
32
+ To search your messages, the command is `curviate inbox search`.
33
+
34
+ - **A mistyped `connect` subcommand no longer sends a connection invitation.**
35
+ Same shape: `curviate connect <id>` sends an invitation, so `connect snet`
36
+ (for `sent`) invited whoever owns the slug `snet`. A first argument that is a
37
+ near-miss of a `connect` subcommand is now refused with a suggestion. If the
38
+ slug really was intended, pass the full profile URL or the provider id.
39
+
40
+ - **`curviate webhook verify` now verifies real deliveries.** It inherited a
41
+ defect from `@curviate/sdk`: the platform sends the event name in `event` and
42
+ the SDK required a `type` field no delivery has ever carried, so verification
43
+ failed on 100% of genuine webhooks and reported `malformed_header`, pointing
44
+ at the header and the secret when both were correct. Fixed by the `@curviate/sdk`
45
+ 0.19.0 dependency bump. A body that verifies but cannot be parsed now reports
46
+ `malformed_payload`.
47
+
48
+ - **`webhook verify --body` accepts the raw body inline, as a file path, or on
49
+ stdin.** Every documented invocation failed before this: the flag value was
50
+ handed straight to the filesystem, so inline JSON was opened as a filename
51
+ (`ENOENT`/`ENAMETOOLONG`), and `--body -` opened the internal placeholder the
52
+ argument parser substitutes for a bare dash. Only a file path ever worked, and
53
+ the failure surfaced as exit `1` with a raw filesystem error rather than a
54
+ usage error. A body is now recognised by its leading `{`, `-` reads stdin on
55
+ every platform, and anything unreadable exits `2` naming all three accepted
56
+ forms. A trailing newline added by a shell redirect, `curl -o`, or an editor
57
+ save is ignored; newlines inside the body are preserved.
58
+
59
+ - **`webhook verify --help` named the wrong signature header.** It said
60
+ `X-Curviate-Signature`; the platform sends `Curviate-Signature`, so a reader
61
+ who copied the name read for a header that is never present. The same help
62
+ also claimed `--header` falls back to stdin, which it never did. Both flags
63
+ are now marked required, so omitting one is a usage error instead of a
64
+ signature failure that points at a secret which was never wrong.
65
+
66
+ - **`group list` help said `--profile`; the flag is `--target`.** `--profile` is
67
+ the global config-profile selector, so following the help text selected a
68
+ config profile and silently returned your own groups instead of the target
69
+ member's.
70
+
71
+ - **`job publish` and `recruiter job publish` help said `--budget-*`.** The glob
72
+ is not a flag anyone can type; the help now names `--budget-amount`,
73
+ `--budget-currency`, and `--budget-scope`.
74
+
75
+ - **Em dashes removed from all CLI output** (error messages, help text, usage
76
+ blocks), including the `no API key` error.
77
+
78
+ ### Changed
79
+
80
+ - `@curviate/sdk` dependency bumped to `^0.19.0`. If you also call the SDK
81
+ directly, note that its webhook event discriminant moved from `event.type` to
82
+ `event.event`; see the SDK's 0.19.0 changelog.
83
+
84
+ ### Added
85
+
86
+ - Two mechanical guards in the test suite, both derived from the live command
87
+ tree rather than a hand-maintained list, so this class of defect cannot
88
+ quietly return:
89
+ - a fall-through audit that forces an explicit safe/read-only/write-bearing
90
+ verdict on every command group that mixes subcommands with a positional
91
+ fallback, and
92
+ - a help-vs-args check that fails when any command's help text names a flag
93
+ that command does not declare (resolving cross-references, so a reference
94
+ to another command's flag is checked against that command).
95
+
96
+ - Two further mechanical guards, in the same spirit:
97
+ - an em-dash check that walks the TypeScript syntax tree, so it inspects
98
+ string and template literals only and leaves comments exempt structurally
99
+ rather than by a fragile line-based heuristic, and
100
+ - a pre-publish binary gate that runs every documented `webhook verify --body`
101
+ form against a recorded delivery: the exact body bytes and the exact
102
+ signature header the platform put on the wire, replayed verbatim and never
103
+ re-signed. Tampering with a single field is still rejected, so the gate
104
+ proves the verdict as well as the plumbing.
105
+
11
106
  ## [0.19.0] - 2026-07-29
12
107
 
13
108
  A minor release closing the remaining SDK-parity gap: 25 new commands across seven
@@ -26,12 +26,12 @@ import {
26
26
  renderSuccess,
27
27
  renderUnexpectedError,
28
28
  resolveEffectiveConfig
29
- } from "./chunk-M33MI53G.js";
29
+ } from "./chunk-HDQUEFD2.js";
30
30
  import {
31
31
  GLOBAL_FLAGS,
32
32
  READ_SINGLE_FLAGS,
33
33
  WRITE_SINGLE_FLAGS
34
- } from "./chunk-TMU3CSPR.js";
34
+ } from "./chunk-DW2HCRXJ.js";
35
35
 
36
36
  // src/commands/account.ts
37
37
  import { defineCommand } from "citty";
@@ -90,15 +90,15 @@ function checkCredentialConflicts(flags, out) {
90
90
  const passwordStdin = flags["password-stdin"] ?? false;
91
91
  const liAtStdin = flags["li-at-stdin"] ?? false;
92
92
  if (flags.password !== void 0 && passwordStdin) {
93
- failConflict(out, "--password and --password-stdin cannot both be set \u2014 choose one source.");
93
+ failConflict(out, "--password and --password-stdin cannot both be set. Choose one source.");
94
94
  }
95
95
  if (flags["li-at"] !== void 0 && liAtStdin) {
96
- failConflict(out, "--li-at and --li-at-stdin cannot both be set \u2014 choose one source.");
96
+ failConflict(out, "--li-at and --li-at-stdin cannot both be set. Choose one source.");
97
97
  }
98
98
  if (passwordStdin && liAtStdin) {
99
99
  failConflict(
100
100
  out,
101
- "--password-stdin and --li-at-stdin cannot both be set \u2014 stdin can feed only one secret per invocation."
101
+ "--password-stdin and --li-at-stdin cannot both be set. stdin can feed only one secret per invocation."
102
102
  );
103
103
  }
104
104
  if (liAtStdin && flags["auth-method"] === "credentials") {
@@ -164,7 +164,7 @@ function challengeDescription(type) {
164
164
  case "two_factor_app":
165
165
  return "Enter the current code from your authenticator app.";
166
166
  case "mobile_app_approval":
167
- return "Open your LinkedIn mobile app and approve this sign-in \u2014 this will continue automatically once you tap approve.";
167
+ return "Open your LinkedIn mobile app and approve this sign-in. This will continue automatically once you tap approve.";
168
168
  }
169
169
  }
170
170
  function printChallengeCopy(type, out) {
@@ -293,7 +293,7 @@ async function buildAuthBody(flags, ctx) {
293
293
  // of --email — an email-less body still fails downstream validation,
294
294
  // as expected, rather than the flag being silently ignored.
295
295
  allowInteractiveStdinRead: !ctx.previewMode,
296
- failMessage: "no password \u2014 pass --password, --password-stdin, or set CURVIATE_LINKEDIN_PASSWORD",
296
+ failMessage: "no password. Pass --password, --password-stdin, or set CURVIATE_LINKEDIN_PASSWORD",
297
297
  prompt: { isTTY: ctx.isTTY, readline: ctx.readline, promptText: "LinkedIn password: " },
298
298
  out: ctx.out
299
299
  });
@@ -314,7 +314,7 @@ async function buildAuthBody(flags, ctx) {
314
314
  readSingleLine: ctx.readSingleLine,
315
315
  required: true,
316
316
  allowInteractive: !ctx.previewMode,
317
- failMessage: "no li_at \u2014 pass --li-at, --li-at-stdin, or set CURVIATE_LINKEDIN_LI_AT",
317
+ failMessage: "no li_at. Pass --li-at, --li-at-stdin, or set CURVIATE_LINKEDIN_LI_AT",
318
318
  out: ctx.out
319
319
  });
320
320
  const liA = await resolveSecret({
@@ -377,7 +377,7 @@ function printConnected(result, ctx) {
377
377
  }
378
378
  function printInvalidCodeRetryHint(err, out) {
379
379
  const attemptsRemaining = err.attempts_remaining;
380
- const message = typeof attemptsRemaining === "number" ? `That code was not accepted \u2014 ${attemptsRemaining} attempt(s) remaining.` : "That code was not accepted \u2014 please try again.";
380
+ const message = typeof attemptsRemaining === "number" ? `That code was not accepted. ${attemptsRemaining} attempt(s) remaining.` : "That code was not accepted. Please try again.";
381
381
  out.stderr.write(`${message}
382
382
  `);
383
383
  }
@@ -620,7 +620,7 @@ async function runAccountConnectSessionPoll(client, flags, out, io = {}) {
620
620
  }
621
621
  renderSuccess(outcome.result, outOpts, out);
622
622
  out.stderr.write(
623
- `Still waiting for the account to connect \u2014 the wait window elapsed. Check again: curviate account connect-session poll --session ${flags.session} --wait
623
+ `Still waiting for the account to connect. The wait window elapsed. Check again: curviate account connect-session poll --session ${flags.session} --wait
624
624
  `
625
625
  );
626
626
  process.exit(AUTH_NEEDED);
@@ -851,7 +851,7 @@ async function runAccountCheckpointPoll(client, flags, out, io = {}) {
851
851
  }
852
852
  renderSuccess(outcome.result, outOpts, out);
853
853
  out.stderr.write(
854
- `Still waiting for approval \u2014 the wait window elapsed. Check again: curviate account checkpoint poll ${accountId} --wait
854
+ `Still waiting for approval. The wait window elapsed. Check again: curviate account checkpoint poll ${accountId} --wait
855
855
  `
856
856
  );
857
857
  process.exit(AUTH_NEEDED);
@@ -868,7 +868,7 @@ var accountListCommand = defineCommand({
868
868
  profile: flags.profile
869
869
  });
870
870
  if (!cfg.apiKey) {
871
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
871
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
872
872
  process.exit(3);
873
873
  }
874
874
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -892,7 +892,7 @@ var accountGetCommand = defineCommand({
892
892
  profile: flags.profile
893
893
  });
894
894
  if (!cfg.apiKey) {
895
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
895
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
896
896
  process.exit(3);
897
897
  }
898
898
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -924,10 +924,10 @@ var accountLinkCommand = defineCommand({
924
924
  "proxy-password": { type: "string", description: `Proxy auth password. ${OPTIONAL_SECRET_WARNING("CURVIATE_PROXY_PASSWORD")}` },
925
925
  "user-agent": { type: "string", description: "Browser User-Agent to pin for this account." },
926
926
  "recruiter-contract-id": { type: "string", description: "Recruiter contract to bind to (Recruiter tier only)." },
927
- "account-id": { type: "string", description: "Existing account id (acc_\u2026) to re-authenticate IN PLACE \u2014 passing it makes this an in-place reconnect of that account. Omit to connect a NEW account into --seat-id." },
927
+ "account-id": { type: "string", description: "Existing account id (acc_\u2026) to re-authenticate IN PLACE. Passing it makes this an in-place reconnect of that account. Omit to connect a NEW account into --seat-id." },
928
928
  "no-interactive": {
929
929
  type: "boolean",
930
- description: "Never prompt for a checkpoint code \u2014 on a checkpoint, always render the envelope and exit 12, even on a TTY.",
930
+ description: "Never prompt for a checkpoint code. On a checkpoint, always render the envelope and exit 12, even on a TTY.",
931
931
  default: false
932
932
  }
933
933
  },
@@ -940,7 +940,7 @@ var accountLinkCommand = defineCommand({
940
940
  profile: flags.profile
941
941
  });
942
942
  if (!cfg.apiKey) {
943
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
943
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
944
944
  process.exit(3);
945
945
  }
946
946
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -951,7 +951,7 @@ var accountLinkCommand = defineCommand({
951
951
  var accountConnectSessionPollCommand = defineCommand({
952
952
  meta: {
953
953
  name: "poll",
954
- description: "Poll the status of an in-progress connect (auth intent) for completion. Without --wait: a single poll \u2014 the JSON `status` field (pending | resolved | expired | failed) tells you what to do next. With --wait: block on the same adaptive cadence as `account link` until a terminal state."
954
+ description: "Poll the status of an in-progress connect (auth intent) for completion. Without --wait: a single poll. The JSON `status` field (pending | resolved | expired | failed) tells you what to do next. With --wait: block on the same adaptive cadence as `account link` until a terminal state."
955
955
  },
956
956
  args: {
957
957
  ...WRITE_SINGLE_FLAGS,
@@ -982,7 +982,7 @@ var accountConnectSessionPollCommand = defineCommand({
982
982
  profile: flags.profile
983
983
  });
984
984
  if (!cfg.apiKey) {
985
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
985
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
986
986
  process.exit(3);
987
987
  }
988
988
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -1004,7 +1004,7 @@ var accountUpdateCommand = defineCommand({
1004
1004
  args: {
1005
1005
  ...WRITE_SINGLE_FLAGS,
1006
1006
  "account-id": { type: "positional", description: "Account id (acc_\u2026)." },
1007
- metadata: { type: "string", description: `Custom metadata as a JSON object (flat string\u2192string map) \u2014 replaces the store wholesale. Example: '{"team":"growth"}'.` },
1007
+ metadata: { type: "string", description: `Custom metadata as a JSON object (flat string\u2192string map). Replaces the store wholesale. Example: '{"team":"growth"}'.` },
1008
1008
  "clear-proxy": { type: "boolean", description: "Clear the custom proxy (revert to automatic proxy protection). Mutually exclusive with --proxy-host.", default: false },
1009
1009
  "proxy-protocol": { type: "string", description: "Proxy protocol: http | https | socks5." },
1010
1010
  "proxy-host": { type: "string", description: "Proxy host or IP." },
@@ -1021,7 +1021,7 @@ var accountUpdateCommand = defineCommand({
1021
1021
  profile: flags.profile
1022
1022
  });
1023
1023
  if (!cfg.apiKey) {
1024
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1024
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
1025
1025
  process.exit(3);
1026
1026
  }
1027
1027
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -1044,7 +1044,7 @@ var accountDisconnectCommand = defineCommand({
1044
1044
  profile: flags.profile
1045
1045
  });
1046
1046
  if (!cfg.apiKey) {
1047
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1047
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
1048
1048
  process.exit(3);
1049
1049
  }
1050
1050
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -1075,7 +1075,7 @@ var accountCheckpointSolveCommand = defineCommand({
1075
1075
  profile: flags.profile
1076
1076
  });
1077
1077
  if (!cfg.apiKey) {
1078
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1078
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
1079
1079
  process.exit(3);
1080
1080
  }
1081
1081
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -1105,7 +1105,7 @@ var accountCheckpointPollCommand = defineCommand({
1105
1105
  // --timeout`. Default: the time remaining to the checkpoint's own expiry.
1106
1106
  timeout: {
1107
1107
  type: "string",
1108
- description: "Wait-loop timeout in milliseconds (requires --wait; default: time remaining to the checkpoint's expiry). Note the unit: this is milliseconds \u2014 `inbox sync-chat --timeout` is seconds."
1108
+ description: "Wait-loop timeout in milliseconds (requires --wait; default: time remaining to the checkpoint's expiry). Note the unit: this is milliseconds; `inbox sync-chat --timeout` is seconds."
1109
1109
  }
1110
1110
  },
1111
1111
  async run({ args }) {
@@ -1116,7 +1116,7 @@ var accountCheckpointPollCommand = defineCommand({
1116
1116
  profile: flags.profile
1117
1117
  });
1118
1118
  if (!cfg.apiKey) {
1119
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1119
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
1120
1120
  process.exit(3);
1121
1121
  }
1122
1122
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -1127,7 +1127,7 @@ var accountCheckpointPollCommand = defineCommand({
1127
1127
  var accountCheckpointRequestCommand = defineCommand({
1128
1128
  meta: {
1129
1129
  name: "request",
1130
- description: "Re-request the challenge notification for a pending checkpoint (e.g. re-send an OTP email, SMS code, or mobile-app approval push). Not every challenge type supports it \u2014 an authenticator- app code has nothing to re-send. The response's `resent` boolean tells you honestly whether a new notification actually went out; the command still exits 0 either way."
1130
+ description: "Re-request the challenge notification for a pending checkpoint (e.g. re-send an OTP email, SMS code, or mobile-app approval push). Not every challenge type supports it; an authenticator- app code has nothing to re-send. The response's `resent` boolean tells you honestly whether a new notification actually went out; the command still exits 0 either way."
1131
1131
  },
1132
1132
  args: {
1133
1133
  ...WRITE_SINGLE_FLAGS,
@@ -1145,7 +1145,7 @@ var accountCheckpointRequestCommand = defineCommand({
1145
1145
  profile: flags.profile
1146
1146
  });
1147
1147
  if (!cfg.apiKey) {
1148
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1148
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
1149
1149
  process.exit(3);
1150
1150
  }
1151
1151
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -87,7 +87,7 @@ async function renameProfile(oldName, newName) {
87
87
  }
88
88
  if (cfg.profiles[newName]) {
89
89
  throw new Error(
90
- `Profile "${newName}" already exists \u2014 remove it first or choose another name.`
90
+ `Profile "${newName}" already exists. Remove it first or choose another name.`
91
91
  );
92
92
  }
93
93
  cfg.profiles[newName] = cfg.profiles[oldName];
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readConfig
4
- } from "./chunk-TMU3CSPR.js";
4
+ } from "./chunk-DW2HCRXJ.js";
5
5
 
6
6
  // src/lib/resolve.ts
7
7
  var DEFAULT_BASE_URL = "https://api.curviate.com";
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/bare-form-guard.ts
4
+ var COMMAND_WORD_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
5
+ function looksLikeCommandWord(token) {
6
+ return COMMAND_WORD_RE.test(token);
7
+ }
8
+ function editDistance(a, b) {
9
+ const m = a.length;
10
+ const n = b.length;
11
+ if (m === 0) return n;
12
+ if (n === 0) return m;
13
+ const d = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
14
+ for (let i = 0; i <= m; i++) d[i][0] = i;
15
+ for (let j = 0; j <= n; j++) d[0][j] = j;
16
+ for (let i = 1; i <= m; i++) {
17
+ for (let j = 1; j <= n; j++) {
18
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
19
+ let best = Math.min(
20
+ d[i - 1][j] + 1,
21
+ // deletion
22
+ d[i][j - 1] + 1,
23
+ // insertion
24
+ d[i - 1][j - 1] + cost
25
+ // substitution
26
+ );
27
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
28
+ best = Math.min(best, d[i - 2][j - 2] + 1);
29
+ }
30
+ d[i][j] = best;
31
+ }
32
+ }
33
+ return d[m][n];
34
+ }
35
+ function nearestSubcommand(token, subcommandNames) {
36
+ let best = null;
37
+ for (const name of subcommandNames) {
38
+ const threshold = name.length < 6 ? 1 : 2;
39
+ const isPrefix = token.length >= 2 && token.length < name.length && name.startsWith(token);
40
+ const distance = isPrefix ? 1 : editDistance(token, name);
41
+ if (distance > threshold) continue;
42
+ if (best === null || distance < best.distance) best = { name, distance };
43
+ }
44
+ return best?.name ?? null;
45
+ }
46
+
47
+ export {
48
+ looksLikeCommandWord,
49
+ nearestSubcommand
50
+ };
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ looksLikeCommandWord,
4
+ nearestSubcommand
5
+ } from "./chunk-HOQ7EUHJ.js";
2
6
  import {
3
7
  resolveTextOrStdin
4
8
  } from "./chunk-U7Y4EXHT.js";
@@ -24,11 +28,11 @@ import {
24
28
  renderSuccess,
25
29
  renderUnexpectedError,
26
30
  resolveEffectiveConfig
27
- } from "./chunk-M33MI53G.js";
31
+ } from "./chunk-HDQUEFD2.js";
28
32
  import {
29
33
  READ_SINGLE_FLAGS,
30
34
  WRITE_FLAGS
31
- } from "./chunk-TMU3CSPR.js";
35
+ } from "./chunk-DW2HCRXJ.js";
32
36
 
33
37
  // src/commands/message.ts
34
38
  import { defineCommand } from "citty";
@@ -267,7 +271,7 @@ async function runMessageReact(client, flags, out) {
267
271
  const messageId = flags.messageId ?? "";
268
272
  const reaction = flags.emoji ?? flags.emojiAlias ?? "";
269
273
  if (!reaction) {
270
- out.stderr.write("error: a reaction is required \u2014 pass it as `message react <chat_id> <message_id> <emoji>` (or --emoji <e>).\n");
274
+ out.stderr.write("error: a reaction is required. Pass it as `message react <chat_id> <message_id> <emoji>` (or --emoji <e>).\n");
271
275
  process.exit(2);
272
276
  return;
273
277
  }
@@ -402,7 +406,7 @@ var messageNewCommand = defineCommand({
402
406
  profile: flags.profile
403
407
  });
404
408
  if (!cfg.apiKey) {
405
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
409
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
406
410
  process.exit(3);
407
411
  }
408
412
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -428,7 +432,7 @@ var messageGetCommand = defineCommand({
428
432
  profile: flags.profile
429
433
  });
430
434
  if (!cfg.apiKey) {
431
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
435
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
432
436
  process.exit(3);
433
437
  }
434
438
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -455,7 +459,7 @@ var messageEditCommand = defineCommand({
455
459
  profile: flags.profile
456
460
  });
457
461
  if (!cfg.apiKey) {
458
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
462
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
459
463
  process.exit(3);
460
464
  }
461
465
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -481,7 +485,7 @@ var messageDeleteCommand = defineCommand({
481
485
  profile: flags.profile
482
486
  });
483
487
  if (!cfg.apiKey) {
484
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
488
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
485
489
  process.exit(3);
486
490
  }
487
491
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -513,7 +517,7 @@ var messageReactCommand = defineCommand({
513
517
  profile: flags.profile
514
518
  });
515
519
  if (!cfg.apiKey) {
516
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
520
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
517
521
  process.exit(3);
518
522
  }
519
523
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -541,7 +545,7 @@ var messageAttachmentCommand = defineCommand({
541
545
  profile: flags.profile
542
546
  });
543
547
  if (!cfg.apiKey) {
544
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
548
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
545
549
  process.exit(3);
546
550
  }
547
551
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -577,7 +581,7 @@ var messageInMailCommand = defineCommand({
577
581
  profile: flags.profile
578
582
  });
579
583
  if (!cfg.apiKey) {
580
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
584
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
581
585
  process.exit(3);
582
586
  }
583
587
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -610,7 +614,7 @@ var messageSendCommand = defineCommand({
610
614
  profile: flags.profile
611
615
  });
612
616
  if (!cfg.apiKey) {
613
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
617
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
614
618
  process.exit(3);
615
619
  }
616
620
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -634,7 +638,7 @@ var messageInMailBalanceCommand = defineCommand({
634
638
  profile: flags.profile
635
639
  });
636
640
  if (!cfg.apiKey) {
637
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
641
+ process.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
638
642
  process.exit(3);
639
643
  }
640
644
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
@@ -642,6 +646,38 @@ var messageInMailBalanceCommand = defineCommand({
642
646
  await runMessageInMailBalance(client, { ...flags, account: flags.account ?? cfg.account }, out);
643
647
  }
644
648
  });
649
+ var MESSAGE_SUBCOMMANDS = [
650
+ "new",
651
+ "send",
652
+ "get",
653
+ "edit",
654
+ "delete",
655
+ "react",
656
+ "attachment",
657
+ "inmail",
658
+ "inmail-balance"
659
+ ];
660
+ var MESSAGE_USAGE = 'Usage: curviate message new --to <attendee> "<text>" [--attach <file>\u2026]\n curviate message send <chat_id> "<text>" [--attach <file>\u2026]\n curviate message <chat_id> "<text>" [--attach <file>\u2026]\n curviate message get <chat_id> <message_id>\n curviate message edit <chat_id> <message_id> "<text>"\n curviate message delete <chat_id> <message_id>\n curviate message react <chat_id> <message_id> <emoji>\n curviate message attachment <chat_id> <message_id> <attachment_id> [-o <file>]\n curviate message inmail --to <id> --subject <s> "<text>"\n curviate message inmail-balance\n';
661
+ function guardBareMessageForm(chatId, out) {
662
+ if (!looksLikeCommandWord(chatId)) return;
663
+ const suggestion = nearestSubcommand(chatId, [...MESSAGE_SUBCOMMANDS]);
664
+ out.stderr.write(
665
+ `error: \`${chatId}\` is not a curviate message subcommand, and it is not a chat id (a chat id looks like 2-\u2026 or COMPANY_\u2026, or a linkedin.com/messaging/thread/\u2026 URL).
666
+ `
667
+ );
668
+ if (suggestion) {
669
+ out.stderr.write(`hint: did you mean \`curviate message ${suggestion}\`?
670
+ `);
671
+ } else if (chatId === "search") {
672
+ out.stderr.write("hint: to search your messages, use `curviate inbox search`.\n");
673
+ }
674
+ out.stderr.write(
675
+ `hint: to send to a chat id this check does not recognise, name the action explicitly: curviate message send <chat_id> "<text>".
676
+ `
677
+ );
678
+ out.stderr.write(MESSAGE_USAGE);
679
+ process.exit(2);
680
+ }
645
681
  var messageCommand = defineCommand({
646
682
  meta: {
647
683
  name: "message",
@@ -671,12 +707,12 @@ var messageCommand = defineCommand({
671
707
  },
672
708
  async run({ args }) {
673
709
  const flags = args;
710
+ const out = buildOutputStreams();
674
711
  if (!flags.chatId) {
675
- process.stderr.write(
676
- 'Usage: curviate message new --to <attendee> "<text>" [--attach <file>\u2026]\n curviate message <chat_id> "<text>" [--attach <file>\u2026]\n curviate message get <chat_id> <message_id>\n curviate message edit <chat_id> <message_id> "<text>"\n curviate message delete <chat_id> <message_id>\n curviate message react <chat_id> <message_id> <emoji>\n curviate message attachment <chat_id> <message_id> <attachment_id> [-o <file>]\n curviate message inmail --to <id> --subject <s> "<text>"\n curviate message inmail-balance\n'
677
- );
712
+ out.stderr.write(MESSAGE_USAGE);
678
713
  process.exit(2);
679
714
  }
715
+ guardBareMessageForm(normalizeChatId(flags.chatId), out);
680
716
  const cfg = await resolveEffectiveConfig({
681
717
  apiKey: flags["api-key"],
682
718
  baseUrl: flags["base-url"],
@@ -685,11 +721,10 @@ var messageCommand = defineCommand({
685
721
  profile: flags.profile
686
722
  });
687
723
  if (!cfg.apiKey) {
688
- process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
724
+ out.stderr.write("error: no API key, run `curviate login` or pass --api-key.\n");
689
725
  process.exit(3);
690
726
  }
691
727
  const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
692
- const out = buildOutputStreams();
693
728
  await runMessageSend(client, { ...flags, account: flags.account ?? cfg.account }, out);
694
729
  }
695
730
  });
@@ -706,5 +741,6 @@ export {
706
741
  runMessageAttachment,
707
742
  runMessageInMail,
708
743
  runMessageInMailBalance,
744
+ guardBareMessageForm,
709
745
  messageCommand
710
746
  };