@curviate/cli 0.22.0 → 0.23.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 (32) hide show
  1. package/CHANGELOG.md +85 -1
  2. package/README.md +65 -23
  3. package/dist/{account-QRGWUQXF.js → account-NZZLUI4E.js} +11 -12
  4. package/dist/{chunk-H6XD3F66.js → chunk-766W25QS.js} +1 -1
  5. package/dist/{chunk-ZYURL5VK.js → chunk-AHYUM2XQ.js} +93 -1
  6. package/dist/{chunk-56ORAZJO.js → chunk-LCIUD7S2.js} +1 -1
  7. package/dist/chunk-LCLYFE54.js +182 -0
  8. package/dist/{chunk-QJZ3LWOX.js → chunk-RJTG5YWE.js} +1 -1
  9. package/dist/{chunk-T5LPGAGJ.js → chunk-S6VAMUCC.js} +16 -22
  10. package/dist/{chunk-CUTMZWL6.js → chunk-SYRFLZ4D.js} +2 -94
  11. package/dist/{chunk-JA3NNST6.js → chunk-TKWW6BV6.js} +2 -2
  12. package/dist/{chunk-DMQZEPQE.js → chunk-UEAX6KUB.js} +10 -0
  13. package/dist/cli.js +107 -68
  14. package/dist/{comment-MCGXFMN4.js → comment-SXYTJPXI.js} +21 -25
  15. package/dist/{company-WDDLWP7E.js → company-GZ2QR2CZ.js} +26 -30
  16. package/dist/{config-FPWWYG7G.js → config-YKQ4QEBI.js} +4 -3
  17. package/dist/{connect-5OROSY5C.js → connect-3LTKVRGM.js} +15 -18
  18. package/dist/{feed-SFVYEAYP.js → feed-ZY27EY52.js} +9 -12
  19. package/dist/{group-HSZY2IJU.js → group-DBE25EID.js} +16 -16
  20. package/dist/{inbox-L6JBGZIB.js → inbox-E6ME3ROZ.js} +14 -17
  21. package/dist/{inboxes-UIO74C5Q.js → inboxes-JRX3J32A.js} +10 -13
  22. package/dist/{job-75ML7R7I.js → job-U5YIJ7KB.js} +19 -22
  23. package/dist/{login-ZLDDIAFW.js → login-ERQVI27Q.js} +3 -3
  24. package/dist/{message-DAQHRBSJ.js → message-NPX7UX46.js} +7 -5
  25. package/dist/{notification-DYJN4ZHN.js → notification-XSSA5QOJ.js} +11 -14
  26. package/dist/{post-RGDOCGC4.js → post-OSM7JH65.js} +22 -26
  27. package/dist/{profile-TJS2YAZD.js → profile-LQXOC7HY.js} +25 -28
  28. package/dist/{recruiter-NSM33IEK.js → recruiter-ZUTWJHO5.js} +34 -38
  29. package/dist/{sales-nav-5O7FR57S.js → sales-nav-LSUBIJWZ.js} +22 -25
  30. package/dist/{search-TDSLK2MB.js → search-FEHXVTQ3.js} +30 -33
  31. package/dist/{webhook-7ESDSIRU.js → webhook-C43C5BK7.js} +5 -10
  32. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -6,7 +6,91 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
  Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html):
7
7
  a new command or flag is a minor; a breaking command/flag/exit-code change is a major; a fix is a patch.
8
8
 
9
- ## [Unreleased]
9
+ ## [0.23.1] - 2026-08-10
10
+
11
+ ### Fixed
12
+
13
+ - **A global flag placed before the subcommand was rejected, and any flag
14
+ value beginning with `-` was rejected.** `curviate --api-key <key> account
15
+ list` exited 2 with "unknown command", and a value like `--api-key
16
+ -something` exited 2 with "unknown flag". Both shared one root cause: the
17
+ dispatcher's flag scan never tracked which tokens it had already consumed
18
+ as another flag's value, so it re-inspected a value token as if it were
19
+ fresh input, misreading a flag's value as the subcommand name in the first
20
+ case and re-scanning a dash-prefixed value as its own (unknown) flag in the
21
+ second. Fixing the scan surfaced a second, more serious defect on the same
22
+ path: once a leading global flag was correctly found, the token AFTER it
23
+ was dropped entirely on the way to the resolved command, so the flag's
24
+ value silently never reached the request at all (a stored profile or
25
+ environment variable answered instead, with no error). All three are
26
+ fixed. Global flags now work in any position, and a flag value starting
27
+ with `-` (an API key, a negative `--limit`, a message beginning with a
28
+ dash) is bound exactly as typed.
29
+
30
+ ## [0.23.0] - 2026-08-10
31
+
32
+ A security and correctness release. Upgrade promptly if `--account` is ever
33
+ set from anything other than a literal you typed yourself (an environment
34
+ variable, a config profile, or agent- or model-generated text): every
35
+ published version through 0.22.0 interpolated it into the request path with
36
+ no validation at all, so a value carrying a slash, `..`, a question mark, a
37
+ hash, or a percent sign could redirect a request, including a write, to a
38
+ different endpoint on the API host. This release also lets `--account` take a
39
+ connected account's name, not just its id.
40
+
41
+ ### Fixed
42
+
43
+ - **`--account` could redirect a request to a different endpoint.**
44
+ `inbox mark-read` on `--account 'x/../../../v1/accounts'` built
45
+ `PATCH /v1/accounts/chats/chat_1` instead of touching the account the caller
46
+ named. `--account 'a?x=1'` injected a query string into the middle of the
47
+ path. Neither needed a literal `..`: the URL Standard percent-decodes when it
48
+ decides whether a segment is a double-dot path segment, so `%2e%2e` walked up
49
+ the path with no slash in the value at all. Every account-scoped command was
50
+ reachable this way, since all of them build the request URL from `--account`.
51
+ The value is now checked before any request is built, and a value that could
52
+ redirect one is refused with `[INVALID_PATH_SEGMENT]` (exit `2`) naming the
53
+ character it contains, rather than being sent.
54
+
55
+ - **`group get` / `group members` never actually accepted a group URL,
56
+ despite documenting it.** The help and this file both claimed the server
57
+ extracted the numeric id from a full `https://www.linkedin.com/groups/...`
58
+ URL passed through verbatim; it does not, and cannot: the URL's own slashes
59
+ split it into several path segments and the request landed on a route that
60
+ does not exist. The numeric id is now extracted client-side, the same way
61
+ member, company, chat, and job URLs already are. A bare numeric id is
62
+ unaffected.
63
+
64
+ ### Added
65
+
66
+ - **`--account` accepts a connected account's name, not just its id.** A value
67
+ that is not shaped like an account id is looked up against
68
+ `accounts.list`: an exact name match wins outright, otherwise a unique
69
+ prefix match resolves. A name matching more than one connected account
70
+ exits with `[ACCOUNT_AMBIGUOUS]` (exit `2`) rather than guessing, since
71
+ acting on the wrong live persona cannot be undone. A name matching none
72
+ exits with `[ACCOUNT_NAME_NOT_FOUND]` (exit `4`) and lists what is
73
+ connected. On an API key with more connected accounts than the resolver
74
+ reads (250 per page, 10 pages), a name lookup exits with
75
+ `[ACCOUNT_LIST_TRUNCATED]` (exit `2`) instead of matching against a list
76
+ that might be missing the very account the name would have matched; pass
77
+ the id instead, which needs no lookup. An id-shaped value still costs no
78
+ extra request. `--preview` never issues the lookup, consistent with it
79
+ never calling the API.
80
+
81
+ ### Changed
82
+
83
+ - **`@curviate/sdk` dependency bumped to `^0.20.1`.** ^0.20.0 still resolves
84
+ the vulnerable 0.20.0 build on a fresh install or an old lockfile; 0.20.1 is
85
+ a hard floor at the fix. Percent-encoding of every other command's path
86
+ parameters (chat, job, group, company and member ids, and so on) is the
87
+ SDK's job as of 0.20.0, not this CLI's: a previous CLI-side guard that
88
+ inspected each call's leading string argument has been removed, because it
89
+ could not know which argument actually became a path segment and was wrong
90
+ in both directions, missing a path parameter passed inside an object and
91
+ rejecting a body field that never reached a path (`post save <share URL>`
92
+ was one such false rejection). `--account` is guarded separately, above,
93
+ because it is the one value this CLI genuinely understands the meaning of.
10
94
 
11
95
  ## [0.22.0] - 2026-08-07
12
96
 
package/README.md CHANGED
@@ -47,12 +47,14 @@ curviate --api-key <your-api-key> account list
47
47
  > history. Prefer `curviate login` or the `CURVIATE_API_KEY` environment
48
48
  > variable; reserve `--api-key` for one-off, low-trust contexts.
49
49
 
50
- Get your API key from the [Curviate dashboard](https://docs.curviate.com).
50
+ Get your API key from the [Curviate dashboard](https://app.curviate.com).
51
51
 
52
52
  ## Usage
53
53
 
54
54
  ```
55
- curviate [command] [subcommand] [flags]
55
+ curviate [flags] [command] [subcommand] [flags]
56
+
57
+ Global flags may appear anywhere in the invocation, before the command or after it.
56
58
 
57
59
  Global flags available on every command:
58
60
  --account Target a specific account ID
@@ -62,9 +64,11 @@ Global flags available on every command:
62
64
  --fields Comma-separated list of fields to include in JSON output
63
65
  --limit Maximum number of results to return per page
64
66
  --cursor Pagination cursor from a previous response
65
- --all Fetch all pages (streams results)
67
+ --all Stream all pages as NDJSON
66
68
  --max-pages Cap on the number of pages fetched with --all
69
+ --page-delay Milliseconds to pause between pages when --all is used (default 400)
67
70
  --preview Show what would happen without sending any write request
71
+ --verbose Output the full SDK response instead of the slim default
68
72
  --base-url Override the API base URL (for testing)
69
73
  --timeout Request timeout in milliseconds (default: 30000)
70
74
  ```
@@ -77,44 +81,59 @@ These examples show how coding agents compose the CLI in real workflows.
77
81
 
78
82
  ### 1. Find people and send connection requests
79
83
 
80
- Search for matching profiles, preview the invitations, then send them once satisfied:
84
+ Search for matching profiles, preview the invitations, then send them once satisfied.
85
+ `--location` on `search people` takes location **ids**, not free text; resolve a
86
+ human-readable place name to an id first with `curviate search parameters --type LOCATION`:
81
87
 
82
88
  ```bash
83
- # Look first: a search is a read, so it just runs (no --preview on reads)
89
+ curviate search parameters --type LOCATION --keywords "Berlin" --account acc_1 --json
90
+ # {"items":[{"id":"103035651","name":"Berlin, Germany"}, {"id":"106967730","name":"Berlin, Berlin, Germany"}, ...]}
91
+
92
+ # Look first, a search is a read, so it just runs (no --preview on reads)
84
93
  curviate search people \
85
94
  --keywords "AI engineer" \
86
- --location "Berlin" \
95
+ --location 103035651 \
96
+ --account acc_1 \
87
97
  --limit 10 \
88
98
  --json
89
99
 
90
100
  # Preview a single write before sending, then pipe IDs into connect, one request per person
91
- curviate connect "$SOME_ID" --note "Hi, I'd love to connect." --preview
101
+ curviate connect "$SOME_ID" --account acc_1 --note "Hi, I'd love to connect." --preview
92
102
 
93
- curviate search people --keywords "AI engineer" --location "Berlin" --all \
103
+ curviate search people --keywords "AI engineer" --location 103035651 --account acc_1 --all \
94
104
  | jq -r '.id' \
95
105
  | head -5 \
96
- | xargs -I{} curviate connect {} --note "Hi, I'd love to connect."
106
+ | xargs -I{} curviate connect {} --account acc_1 --note "Hi, I'd love to connect."
97
107
  ```
98
108
 
99
109
  ### 2. Triage the inbox and extract unread threads
100
110
 
101
- Pull the inbox as JSON, filter unread chats, and surface the most recent message from each:
111
+ Pull the inbox, filter unread chats, and surface the most recent message from each.
112
+ `--all` streams **NDJSON** (one JSON object per line), not a `{items:[...]}` envelope or a
113
+ bare array, so pipe each line straight into `jq` with no `.[]`. The chat's own id is `id`
114
+ (`chat_id` only appears nested inside `last_message`, pointing back at its own chat); the
115
+ unread signal is the integer `unread_count`, not a boolean `unread`; and `last_message` has
116
+ no `sender` object, only `sender_id` (a provider id, not a display name), so a
117
+ human-readable sender comes from the chat's own `user.display_name` instead (present on
118
+ 1:1 chats):
102
119
 
103
120
  ```bash
104
- curviate inbox list --json --all \
105
- | jq '[.[] | select(.unread == true) | {chat_id, sender: .last_message.sender, preview: .last_message.text[0:80]}]'
121
+ curviate inbox list --json --all --account acc_1 \
122
+ | jq -c 'select(.unread_count > 0) | {chat_id: .id, sender: (.user.display_name // .last_message.sender_id), preview: .last_message.text[0:80]}'
106
123
  ```
107
124
 
108
125
  ### 3. Warm up a prospect by reacting to their recent posts
109
126
 
110
- Read recent posts from a profile, then react to each, which is useful for ambient warm-up before outreach:
127
+ Read recent posts from a profile, then react to each, useful for ambient warm-up before outreach.
128
+ A post's id is `id` (there is no `post_id` field), and `--posts` returns a `{items:[...]}`
129
+ envelope, not a bare array:
111
130
 
112
131
  ```bash
113
132
  PROFILE_URL="https://www.linkedin.com/in/example"
114
133
 
115
- curviate profile "$PROFILE_URL" --posts --fields post_id --json \
116
- | jq -r '.[].post_id' \
117
- | xargs -I{} curviate post react {} --reaction like
134
+ curviate profile "$PROFILE_URL" --posts --fields id --account acc_1 --json \
135
+ | jq -r '.items[].id' \
136
+ | xargs -I{} curviate post react {} --account acc_1 --reaction like
118
137
  ```
119
138
 
120
139
  ### 4. Check tier entitlement before a Sales Navigator sweep
@@ -122,7 +141,7 @@ curviate profile "$PROFILE_URL" --posts --fields post_id --json \
122
141
  Exit code `5` means the account lacks the required add-on. Branch on it in a script:
123
142
 
124
143
  ```bash
125
- curviate sales-nav search people --keywords "VP Engineering" --json \
144
+ curviate sales-nav search people --keywords "VP Engineering" --account acc_1 --json \
126
145
  || {
127
146
  code=$?
128
147
  if [ "$code" -eq 5 ]; then
@@ -152,11 +171,13 @@ Exit `2` means the signature is invalid or the replay window has expired.
152
171
 
153
172
  ### 6. Export all accounts to a CSV (agent-friendly pipeline)
154
173
 
155
- List every connected account, select key fields, and format as CSV with `jq`:
174
+ List every connected account, select key fields, and format as CSV with `jq`. `--all` streams
175
+ NDJSON, so slurp it into an array first with `jq -s`; the fields are `account_id` and
176
+ `full_name`, not `id` / `name`:
156
177
 
157
178
  ```bash
158
179
  curviate account list --all --json \
159
- | jq -r '["id","name","status"], (.[] | [.id, .name, .status]) | @csv' \
180
+ | jq -s -r '["account_id","full_name","status"], (.[] | [.account_id, .full_name, .status]) | @csv' \
160
181
  > accounts.csv
161
182
  ```
162
183
 
@@ -229,10 +250,13 @@ curviate sales-nav save-lead ACwAAA1234567 --account acc_1 --list 987654
229
250
 
230
251
  ### 3. Start a new Sales Navigator chat
231
252
 
253
+ `--subject` is required for Sales Navigator messaging.
254
+
232
255
  ```bash
233
256
  curviate sales-nav message new \
234
257
  --to ACwAAA1234567 \
235
258
  --account acc_1 \
259
+ --subject "An opportunity at our company" \
236
260
  "Hi, I'd love to connect about an opportunity at our company."
237
261
  ```
238
262
 
@@ -314,8 +338,12 @@ curviate recruiter project-job get "$PROJECT_ID" --account acc_1 --json
314
338
 
315
339
  ### 3. Create a job posting draft, then publish it
316
340
 
317
- `recruiter job create` requires `--project-name` (the hiring project the posting opens) and takes
318
- the v2 job body. `recruiter job publish` is project-scoped and requires `--mode`
341
+ `recruiter job create` requires `--project-name` (the hiring project the posting opens) plus the
342
+ full v2 job body: `--job-title`, `--company-id`/`--company-name`, `--workplace-type`, `--location`,
343
+ `--employment-status`, `--seniority-level`, `--description` (200 characters minimum), `--industry`,
344
+ `--job-function`, and `--apply-method`. `--location`/`--industry`/`--job-function` take resolved
345
+ parameter ids, the same `search parameters --type LOCATION`/`--type INDUSTRY`/`--type JOB_FUNCTION`
346
+ resolution from example 1 above. `recruiter job publish` is project-scoped and requires `--mode`
319
347
  (`FREE | PROMOTED | PROMOTED_PLUS`); the paid modes also require the full `--budget-*` triple.
320
348
 
321
349
  ```bash
@@ -323,8 +351,15 @@ curviate recruiter job create \
323
351
  --account acc_1 \
324
352
  --project-name "Backend Hiring 2026" \
325
353
  --job-title "Senior Backend Engineer" \
326
- --description "Remote-first team building the core platform." \
354
+ --company-name "Curviate GmbH" \
355
+ --workplace-type REMOTE \
356
+ --location 103035651 \
327
357
  --employment-status FULL_TIME \
358
+ --seniority-level MID_SENIOR_LEVEL \
359
+ --description "We are looking for a senior backend engineer to join our remote-first team building the core platform that powers agent-native LinkedIn automation for thousands of developers and their AI agents worldwide." \
360
+ --industry 96 \
361
+ --job-function 15 \
362
+ --apply-method linkedin \
328
363
  --json
329
364
 
330
365
  curviate recruiter job publish "$PROJECT_ID" "$JOB_ID" --account acc_1 --mode FREE --json
@@ -380,7 +415,7 @@ curviate recruiter message new \
380
415
  --to AEM789 \
381
416
  --account acc_1 \
382
417
  --subject "A role you'd be a great fit for" \
383
- --signature "- Alex, Talent Team" \
418
+ --signature "Alex, Talent Team" \
384
419
  "Hi, I came across your profile and think you'd be a great fit for a role we're hiring for."
385
420
  ```
386
421
 
@@ -407,6 +442,13 @@ curviate recruiter job get "https://www.linkedin.com/jobs/view/4428113858" --acc
407
442
  | 3 | Authentication or authorization failure |
408
443
  | 4 | Resource not found |
409
444
  | 5 | Feature requires an add-on or higher plan |
445
+ | 6 | Rate limited |
446
+ | 7 | Transient platform error (retry likely to succeed) |
447
+ | 8 | Account or connection state blocks the request |
448
+ | 9 | Checkpoint flow error (expired, invalid code, or too many attempts) |
449
+ | 10 | Messaging window expired or recipient unreachable |
450
+ | 11 | Billing issue (payment required, failed, or seat cancelled) |
451
+ | 12 | Auth action needed (a pending checkpoint; not an error) |
410
452
 
411
453
  ## License
412
454
 
@@ -2,9 +2,6 @@
2
2
  import {
3
3
  readlineSync
4
4
  } from "./chunk-H4KV7MIN.js";
5
- import {
6
- AUTH_NEEDED
7
- } from "./chunk-PMRQXBCP.js";
8
5
  import {
9
6
  slimAccountGet,
10
7
  slimAccountList,
@@ -13,7 +10,7 @@ import {
13
10
  import {
14
11
  pageDelayFromFlags,
15
12
  streamAll
16
- } from "./chunk-H6XD3F66.js";
13
+ } from "./chunk-766W25QS.js";
17
14
  import {
18
15
  buildPreviewOutput
19
16
  } from "./chunk-R3VLWLVV.js";
@@ -23,15 +20,17 @@ import {
23
20
  renderSuccess,
24
21
  renderUnexpectedError,
25
22
  resolveEffectiveConfig
26
- } from "./chunk-JA3NNST6.js";
23
+ } from "./chunk-TKWW6BV6.js";
24
+ import "./chunk-SYRFLZ4D.js";
27
25
  import {
28
26
  GLOBAL_FLAGS,
29
27
  READ_SINGLE_FLAGS,
30
- WRITE_SINGLE_FLAGS
31
- } from "./chunk-CUTMZWL6.js";
32
- import {
28
+ WRITE_SINGLE_FLAGS,
33
29
  defaultReadStdin
34
- } from "./chunk-ZYURL5VK.js";
30
+ } from "./chunk-AHYUM2XQ.js";
31
+ import {
32
+ AUTH_NEEDED
33
+ } from "./chunk-PMRQXBCP.js";
35
34
 
36
35
  // src/commands/account.ts
37
36
  import { defineCommand } from "citty";
@@ -1101,11 +1100,11 @@ var accountCheckpointPollCommand = defineCommand({
1101
1100
  },
1102
1101
  // Override WRITE_SINGLE_FLAGS.timeout: on this command --timeout is the
1103
1102
  // --wait loop's own wall-clock bound in MILLISECONDS (requires --wait),
1104
- // NOT the SDK request timeout, and NOT seconds like `inbox sync-chat
1105
- // --timeout`. Default: the time remaining to the checkpoint's own expiry.
1103
+ // NOT the SDK request timeout. Default: the time remaining to the
1104
+ // checkpoint's own expiry.
1106
1105
  timeout: {
1107
1106
  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; `inbox sync-chat --timeout` is seconds."
1107
+ description: "Wait-loop timeout in milliseconds (requires --wait; default: time remaining to the checkpoint's expiry)."
1109
1108
  }
1110
1109
  },
1111
1110
  async run({ args }) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  renderNotices
4
- } from "./chunk-JA3NNST6.js";
4
+ } from "./chunk-TKWW6BV6.js";
5
5
 
6
6
  // src/lib/paginate.ts
7
7
  var PaginateError = class extends Error {
@@ -73,11 +73,103 @@ async function resolveTextOrStdin(rawText, out, readStdin) {
73
73
  return text;
74
74
  }
75
75
 
76
+ // src/lib/global-flags.ts
77
+ var GLOBAL_FLAGS = {
78
+ // Auth / config
79
+ "api-key": {
80
+ type: "string",
81
+ description: "API key (overrides env and profile). Note: a key passed on the command line is visible to other processes via `ps` and is saved in shell history; prefer the CURVIATE_API_KEY env var or `curviate login`."
82
+ },
83
+ profile: {
84
+ type: "string",
85
+ description: "Named profile to use from the config file."
86
+ },
87
+ account: {
88
+ type: "string",
89
+ description: "Account id for account-scoped commands."
90
+ },
91
+ "base-url": {
92
+ type: "string",
93
+ description: "Override the API base URL."
94
+ },
95
+ timeout: {
96
+ type: "string",
97
+ description: "Request timeout in milliseconds."
98
+ },
99
+ // Output
100
+ json: {
101
+ type: "boolean",
102
+ description: "Emit JSON output (default when stdout is not a TTY).",
103
+ default: false
104
+ },
105
+ fields: {
106
+ type: "string",
107
+ description: "Comma-separated dot-path field projection (e.g. id,name)."
108
+ },
109
+ limit: {
110
+ type: "string",
111
+ description: "Maximum items per page."
112
+ },
113
+ cursor: {
114
+ type: "string",
115
+ description: "Pagination cursor (opaque token from a previous response)."
116
+ },
117
+ all: {
118
+ type: "boolean",
119
+ description: "Stream all pages as NDJSON.",
120
+ default: false
121
+ },
122
+ "max-pages": {
123
+ type: "string",
124
+ description: "Maximum number of pages to fetch when --all is used."
125
+ },
126
+ "page-delay": {
127
+ type: "string",
128
+ 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."
129
+ },
130
+ preview: {
131
+ type: "boolean",
132
+ description: "Render the request that would be sent without calling the API.",
133
+ default: false
134
+ },
135
+ verbose: {
136
+ type: "boolean",
137
+ description: "Output the full SDK response instead of the slim default.",
138
+ default: false
139
+ }
140
+ };
141
+ var WRITE_FLAGS = {
142
+ "api-key": GLOBAL_FLAGS["api-key"],
143
+ profile: GLOBAL_FLAGS.profile,
144
+ account: GLOBAL_FLAGS.account,
145
+ "base-url": GLOBAL_FLAGS["base-url"],
146
+ timeout: GLOBAL_FLAGS.timeout,
147
+ json: GLOBAL_FLAGS.json,
148
+ preview: GLOBAL_FLAGS.preview,
149
+ verbose: GLOBAL_FLAGS.verbose
150
+ };
151
+ var READ_SINGLE_FLAGS = {
152
+ "api-key": GLOBAL_FLAGS["api-key"],
153
+ profile: GLOBAL_FLAGS.profile,
154
+ account: GLOBAL_FLAGS.account,
155
+ "base-url": GLOBAL_FLAGS["base-url"],
156
+ timeout: GLOBAL_FLAGS.timeout,
157
+ json: GLOBAL_FLAGS.json,
158
+ fields: GLOBAL_FLAGS.fields,
159
+ preview: GLOBAL_FLAGS.preview,
160
+ verbose: GLOBAL_FLAGS.verbose
161
+ };
162
+ var WRITE_SINGLE_FLAGS = READ_SINGLE_FLAGS;
163
+
76
164
  export {
77
165
  STDIN_SENTINEL,
78
166
  defaultReadStdin,
79
167
  isStdinToken,
80
168
  restoreLiteralDashes,
81
169
  assertNoStdinPlaceholder,
82
- resolveTextOrStdin
170
+ resolveTextOrStdin,
171
+ GLOBAL_FLAGS,
172
+ WRITE_FLAGS,
173
+ READ_SINGLE_FLAGS,
174
+ WRITE_SINGLE_FLAGS
83
175
  };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  isStdinToken
4
- } from "./chunk-ZYURL5VK.js";
4
+ } from "./chunk-AHYUM2XQ.js";
5
5
 
6
6
  // src/lib/search-filters.ts
7
7
  import { readFile } from "fs/promises";
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ assertNoStdinPlaceholder
4
+ } from "./chunk-AHYUM2XQ.js";
5
+ import {
6
+ getExitCode
7
+ } from "./chunk-PMRQXBCP.js";
8
+
9
+ // src/lib/path-safety.ts
10
+ var UNSAFE_CHARS = /[\u0000-\u0020\u007F/\\?#%]/;
11
+ var UNICODE_WHITESPACE = /[\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]/;
12
+ function describeChar(value, index) {
13
+ const ch = value[index] ?? "";
14
+ const named = {
15
+ "/": "a slash",
16
+ "\\": "a backslash",
17
+ "?": "a question mark",
18
+ "#": "a hash",
19
+ "%": "a percent sign",
20
+ " ": "a space",
21
+ " ": "a tab",
22
+ "\n": "a newline",
23
+ "\r": "a carriage return"
24
+ };
25
+ return named[ch] ?? `the character U+${ch.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
26
+ }
27
+ function firstRedirectingIndex(value) {
28
+ for (let i = 0; i < value.length; i++) {
29
+ const ch = value[i];
30
+ if (ch === " ") continue;
31
+ if (UNSAFE_CHARS.test(ch) || UNICODE_WHITESPACE.test(ch)) return i;
32
+ }
33
+ return -1;
34
+ }
35
+ function redirectingViolation(value) {
36
+ if (value.length === 0) return "it is empty";
37
+ const i = firstRedirectingIndex(value);
38
+ if (i >= 0) return `it contains ${describeChar(value, i)}`;
39
+ if (value.includes("..")) return 'it contains ".."';
40
+ if (value === ".") return 'it is "."';
41
+ return null;
42
+ }
43
+ function pathSegmentViolation(value) {
44
+ const redirecting = redirectingViolation(value);
45
+ if (redirecting !== null) return redirecting;
46
+ if (value.includes(" ")) return "it contains a space";
47
+ return null;
48
+ }
49
+ function pathSegmentErrorMessage(label, value, reason) {
50
+ return `error: [INVALID_PATH_SEGMENT] ${label}: ${reason}, so it cannot be part of a request path. A value carrying a slash, backslash, question mark, hash, percent sign, whitespace, or ".." would redirect the request to a different endpoint. Received: ${JSON.stringify(value)}.
51
+ `;
52
+ }
53
+
54
+ // src/lib/account-arg.ts
55
+ var ACCOUNT_ID_RE = /^acc_[A-Za-z0-9_-]+$/;
56
+ var listCache = /* @__PURE__ */ new WeakMap();
57
+ var MAX_LOOKUP_PAGES = 10;
58
+ function toConnectedAccount(item) {
59
+ if (item === null || typeof item !== "object") return null;
60
+ const row = item;
61
+ const accountId = row["account_id"];
62
+ if (typeof accountId !== "string" || accountId.length === 0) return null;
63
+ const fullName = row["full_name"];
64
+ return {
65
+ accountId,
66
+ fullName: typeof fullName === "string" && fullName.length > 0 ? fullName : null
67
+ };
68
+ }
69
+ function listConnectedAccounts(client) {
70
+ const cached = listCache.get(client);
71
+ if (cached) return cached;
72
+ const pending = (async () => {
73
+ const accounts = [];
74
+ let cursor;
75
+ for (let page = 0; page < MAX_LOOKUP_PAGES; page++) {
76
+ const params = { limit: 250 };
77
+ if (cursor) params["cursor"] = cursor;
78
+ const result = await client.accounts.list(
79
+ params
80
+ );
81
+ for (const item of result.items ?? []) {
82
+ const account = toConnectedAccount(item);
83
+ if (account) accounts.push(account);
84
+ }
85
+ if (!result.cursor) return { accounts, complete: true };
86
+ cursor = result.cursor;
87
+ }
88
+ return { accounts, complete: false };
89
+ })();
90
+ listCache.set(client, pending);
91
+ return pending;
92
+ }
93
+ function describeAccount(account) {
94
+ return account.fullName === null ? account.accountId : `"${account.fullName}" (${account.accountId})`;
95
+ }
96
+ function describeAll(accounts) {
97
+ return accounts.map(describeAccount).join(", ");
98
+ }
99
+ function matchAccounts(accounts, selector) {
100
+ const needle = selector.toLowerCase();
101
+ const byId = accounts.filter((a) => a.accountId === selector);
102
+ if (byId.length > 0) return byId;
103
+ const exactName = accounts.filter((a) => a.fullName?.toLowerCase() === needle);
104
+ if (exactName.length > 0) return exactName;
105
+ return accounts.filter((a) => a.fullName?.toLowerCase().startsWith(needle));
106
+ }
107
+ async function requireAccount(client, flags, out) {
108
+ const account = flags.account;
109
+ if (!account) {
110
+ out.stderr.write(
111
+ "error: --account is required for this command. Set it via --account, CURVIATE_ACCOUNT, or `curviate config set-account`.\n"
112
+ );
113
+ process.exit(2);
114
+ }
115
+ const selector = account.trim();
116
+ assertNoStdinPlaceholder("the --account value", [selector]);
117
+ const redirecting = redirectingViolation(selector);
118
+ if (redirecting !== null) {
119
+ out.stderr.write(pathSegmentErrorMessage("--account", selector, redirecting));
120
+ process.exit(2);
121
+ }
122
+ if (ACCOUNT_ID_RE.test(selector)) return selector;
123
+ if (flags.preview === true) {
124
+ out.stderr.write(
125
+ `note: --preview does not call the API, so --account "${selector}" is shown as you typed it. A real run resolves it to an account id first, and fails if it matches no connected account or more than one.
126
+ `
127
+ );
128
+ return selector;
129
+ }
130
+ let listed;
131
+ try {
132
+ listed = await listConnectedAccounts(client);
133
+ } catch (err) {
134
+ const { CurviateError } = await import("@curviate/sdk");
135
+ if (err instanceof CurviateError) {
136
+ const e = err;
137
+ out.stderr.write(
138
+ `error: [${e.code}] could not look up connected accounts to resolve --account "${selector}": ${e.message}
139
+ `
140
+ );
141
+ process.exit(getExitCode(e.code));
142
+ }
143
+ throw err;
144
+ }
145
+ if (!listed.complete) {
146
+ out.stderr.write(
147
+ `error: [ACCOUNT_LIST_TRUNCATED] --account "${selector}" cannot be resolved by name: this API key has more connected accounts than the resolver reads (it stops after ${MAX_LOOKUP_PAGES} pages of 250), so the name would be matched against an incomplete list and a single match would not prove there is only one. Pass the account id instead; an id is used as given and needs no lookup.
148
+ `
149
+ );
150
+ process.exit(2);
151
+ }
152
+ const accounts = listed.accounts;
153
+ const matches = matchAccounts(accounts, selector);
154
+ if (matches.length === 1) {
155
+ const resolved = matches[0].accountId;
156
+ const bad = pathSegmentViolation(resolved);
157
+ if (bad !== null) {
158
+ out.stderr.write(
159
+ pathSegmentErrorMessage(`the account id resolved from "${selector}"`, resolved, bad)
160
+ );
161
+ process.exit(2);
162
+ }
163
+ return resolved;
164
+ }
165
+ if (matches.length > 1) {
166
+ out.stderr.write(
167
+ `error: [ACCOUNT_AMBIGUOUS] --account "${selector}" matches ${matches.length} connected accounts: ${describeAll(matches)}. Pass the account id of the one you mean; this is not resolved by guessing, because acting as the wrong account cannot be undone.
168
+ `
169
+ );
170
+ process.exit(2);
171
+ }
172
+ const known = accounts.length === 0 ? "No accounts are connected on this API key." : `Connected accounts: ${describeAll(accounts)}.`;
173
+ out.stderr.write(
174
+ `error: [ACCOUNT_NAME_NOT_FOUND] --account "${selector}" matched no connected account. ${known}
175
+ `
176
+ );
177
+ process.exit(4);
178
+ }
179
+
180
+ export {
181
+ requireAccount
182
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  resolveIdentifier
4
- } from "./chunk-DMQZEPQE.js";
4
+ } from "./chunk-UEAX6KUB.js";
5
5
 
6
6
  // src/lib/member-id.ts
7
7
  var MEMBER_PROVIDER_ID_RE = /^A[CDE][A-Za-z0-9_-]{4,}$/;