hedge-broker 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,53 @@ All notable changes to `hedge-broker` are documented here. This project follows
4
4
  [Semantic Versioning](https://semver.org) and
5
5
  [Keep a Changelog](https://keepachangelog.com).
6
6
 
7
+ ## [0.4.0] - 2026-09-19
8
+
9
+ The Hedge broker API rework: creating a submission now starts the run, markets
10
+ come back in three categories, the conversation with Hedge is readable and
11
+ answerable over the API, and binds happen over the API with explicit
12
+ attestation of the assumptions.
13
+
14
+ ### Added
15
+
16
+ - `intake [files...] --text|--text-file [--insured] [--lob] [--state] [--effective] [--producer-email] [--hold]`: send Hedge a risk as free text and/or PDFs (multipart `POST /broker/intake`). Sends an `Idempotency-Key` per run (`--idempotency-key` to supply your own).
17
+ - `markets <submissionId> [--wait]`: `GET /broker/submissions/{id}/markets` rendered in the three categories (Hedge Instant Quote, Hedge Binding, Hedge Specialty) with each lane's status, `needs_from_you`, assumptions (`[ ]` standing / `[x]` confirmed) and released quotes.
18
+ - `thread <submissionId> [--since] [--limit] [--one-page]`: the conversation with Hedge, oldest first, following `next_cursor` to the tail; `--json` returns `{messages, next_cursor, last_cursor}`.
19
+ - `reply <submissionId> <text|-> [--attach ids] [--producer-email]`: reply to Hedge on the thread with an `Idempotency-Key`.
20
+ - `answer-asks <submissionId> [--set k=v ...]`: batch answers to `outstanding-requirements`; without `--set` lists the items and keys. Values are typed from the item's `input` (number, bool) before sending.
21
+ - `withdraw <submissionId>`.
22
+ - `bind <submissionId> --quote <quoteId> [--payment in_full|monthly] [--attest | --attest-keys k1,k2]`: create a bind request. A 409 `assumptions_unconfirmed` prints every assumption and the exact re-run to attest (exit 1; `--json` prints the 409 detail).
23
+ - `bind-status <submissionId> [bindRequestId]`, `bind-upload <submissionId> <bindRequestId> <contingencyId> <file.pdf> [--notes]`, `bind-submit <submissionId> <bindRequestId> [--attest | --attest-keys]`.
24
+ - `programs [--category] [--lob] [--state]` and `program-schema <programId> [--lob] [--state]`: the program catalog with categories and the application question schema for instant-quote programs.
25
+ - `login --client-id <id> --client-secret <secret>` (or `HEDGE_CLIENT_SECRET`): machine sign-in with the `client_credentials` grant (scope `broker_mcp broker_submit`). The token is cached with its expiry and renewed by re-exchange; the secret is never printed.
26
+ - `submit --hold`: keep the draft instead of starting the run.
27
+
28
+ ### Changed
29
+
30
+ - `submit` now starts the run on create (Hedge matches appetite, emails the producer the clearance, opens the lanes and quotes the instant-quote markets), matching the API. Its output shows `marketing_status` and `next_step` and points at `hedge markets`.
31
+ - `finalize` is now "release a held submission": it starts the run for a submission created with `--hold` and is idempotent on one already running.
32
+ - `requirements`, `status` and `finalize --wait` hints point at `hedge markets` and describe held drafts instead of "not yet finalized".
33
+ - `ApiError` carries the parsed response body so commands can act on structured errors.
34
+
35
+ ## [0.3.2] - 2026-07-23
36
+
37
+ ### Fixed
38
+
39
+ - `finalize --wait` polls the requirements view (whose `marketing_status` flips the moment lanes attach) instead of the submission detail, which only lists markets Hedge has already contacted.
40
+
41
+ ## [0.3.1] - 2026-07-23
42
+
43
+ ### Added
44
+
45
+ - `forms [search]` and `form <formKey> [-o file]`: search the blank application-form catalog and download a blank by its key (the keys `hedge requirements` lists).
46
+
47
+ ## [0.3.0] - 2026-07-23
48
+
49
+ ### Added
50
+
51
+ - `finalize --wait [--timeout minutes]`: poll until markets attach, then print them.
52
+ - `requirements` and `status` explain an empty markets list using the server's `marketing_status` (matching vs. not started vs. no appetite) instead of leaving it ambiguous.
53
+
7
54
  ## [0.2.0] - 2026-07-06
8
55
 
9
56
  ### Added
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # hedge
2
2
 
3
- The Hedge broker portal from your terminal. Submit risks, check appetite, run instant carrier quotes, and track status, policies, and payments.
3
+ Send Hedge a risk from your terminal, the way you would email it, and track it to bind. `hedge intake` takes free text and PDFs; Hedge extracts the risk, matches appetite, emails the producer the clearance and opens the market lanes. `hedge markets` shows the three market categories with what each lane still needs from you, the assumptions Hedge made and the released quotes. `hedge thread` is the conversation with Hedge; `hedge reply` answers it. `hedge bind` requests a bind with explicit attestation of the assumptions.
4
4
 
5
5
  ## Install
6
6
 
@@ -35,42 +35,66 @@ The installer downloads the binary for your OS and CPU into `$HOME/.local/bin`.
35
35
  ## Quickstart
36
36
 
37
37
  ```bash
38
- hedge login # device-code sign in (prints a code + URL)
39
- hedge appetite "hvac contractor" --state TX
40
- hedge submit --insured "Acme HVAC" --narrative "Residential HVAC install and repair" --lob commercial_general_liability --state TX
41
- hedge upload <submission-id> ./acord-125.pdf
42
- hedge finalize <submission-id> # start marketing to carriers
43
- hedge status <submission-id>
38
+ hedge login # device-code sign in (prints a code + URL)
39
+ hedge intake ./acord-125.pdf ./loss-runs.pdf \
40
+ --text "Peak Roofing LLC, residential re-roofing in CA, 12 employees, GL and property, eff 10/1"
41
+ hedge markets <submission-id> --wait # the three categories, asks, assumptions, quotes
42
+ hedge thread <submission-id> # the clearance email, questions, quote deliveries
43
+ hedge reply <submission-id> "Payroll is 900k, no work over 3 stories"
44
+ hedge bind <submission-id> --quote <quote-id> # 409 lists the assumptions to attest
45
+ hedge bind <submission-id> --quote <quote-id> --attest # after the agent reviewed each one
44
46
  ```
45
47
 
48
+ Creating a submission starts the run. There is no separate "go shop it" step: `hedge intake` and `hedge submit` both start marketing as soon as the create commits, exactly like emailing Hedge the risk. Pass `--hold` to keep the draft (attach documents, answer requirements), then `hedge finalize <id>` releases it.
49
+
50
+ ## The three market categories
51
+
52
+ `hedge markets` always returns all three, in this order:
53
+
54
+ | Category | Label | What it is |
55
+ | --- | --- | --- |
56
+ | `instant_quote` | Hedge Instant Quote | Connected carrier APIs, quoted on recorded favorable assumptions. Each assumption is a pre-bind attestation the producer must confirm. |
57
+ | `binding` | Hedge Binding | Markets Hedge quotes for you. |
58
+ | `specialty` | Hedge Specialty | Email markets that need the completed ACORDs, program requirements and loss runs. |
59
+
60
+ Each lane carries its status, `submit_ready`, `needs_from_you` (only while the lane is gapped), `assumptions` (`[ ]` still standing, `[x]` confirmed) and released firm `quotes` with their `quote_id`.
61
+
46
62
  ## Commands
47
63
 
48
64
  | Command | What it does |
49
65
  | --- | --- |
50
- | `hedge appetite <class> [--state ST] [--lob slug]` | Which markets have appetite for a class of business. |
51
- | `hedge market-requirements <marketId> --lob <slug> [--state ST] [--programs keys]` | What a market needs to quote a line: the application package plus per-program requirements. Market ids come from `hedge appetite --json`. |
52
- | `hedge submit --insured <name> --narrative <text> [flags]` | Create a submission (does not market it yet). Applicant, contact, address, and exposure flags are listed in `hedge submit --help`; `--body <file\|->` sends a full JSON body, with explicit flags taking precedence. |
53
- | `hedge upload <submissionId> <file.pdf> [--name label]` | Attach an ACORD, loss runs, or supplement. |
54
- | `hedge requirements <submissionId>` | What the submission still needs (per market, forms, carrier questions). |
55
- | `hedge finalize <submissionId>` | Start marketing the submission to carriers. |
66
+ | `hedge intake [files...] [--text <text>\|--text-file <path\|->] [--insured <name>] [--lob <slugs>] [--state <ST>] [--effective <date>] [--producer-email <email>] [--hold]` | Send Hedge a risk as text and/or PDFs (up to 20, 15 MB each). Creates the submission and starts the run. Requires `--text`/`--text-file` or `--insured`. |
67
+ | `hedge submit --insured <name> --narrative <text> [flags] [--hold]` | Create a submission from structured fields and start the run. `--body <file\|->` sends a full JSON body, with explicit flags taking precedence; see `hedge submit --help`. |
68
+ | `hedge markets <submissionId> [--wait] [--timeout min]` | The markets being tried in the three categories, with status, `needs_from_you`, assumptions and quotes. `--wait` polls until a lane attaches (usually ~5 minutes after create). |
69
+ | `hedge thread <submissionId> [--since cursor] [--limit n] [--one-page]` | The conversation with Hedge, oldest first. Follows `next_cursor` to the tail; `--json` returns `{messages, next_cursor, last_cursor}`. |
70
+ | `hedge reply <submissionId> <text\|-> [--attach ids] [--producer-email]` | Reply to Hedge exactly as you would reply to its email: answer a question, send a revision, authorize a bind. Hedge answers on the thread within a couple of minutes. |
71
+ | `hedge answer-asks <submissionId> [--set k=v ...] [--producer-email]` | Answer the outstanding items the matched markets still need, in one batch. Without `--set` it lists the items and their keys; values are typed per the item's input. |
72
+ | `hedge withdraw <submissionId> [--producer-email]` | Withdraw: a held draft is deleted, a live placement is pulled from every engaged market. |
73
+ | `hedge bind <submissionId> --quote <quoteId> [--payment in_full\|monthly] [--attest \| --attest-keys k1,k2] [--producer-email]` | Request to bind a released quote. While assumptions stand unconfirmed the API answers 409 `assumptions_unconfirmed`; the CLI prints them and the exact re-run with `--attest`. |
74
+ | `hedge bind-status <submissionId> [bindRequestId]` | Bind requests on a submission, or one in full: status, unconfirmed assumptions, contingencies with upload paths, next steps. |
75
+ | `hedge bind-upload <submissionId> <bindRequestId> <contingencyId> <file.pdf> [--notes]` | Attach a PDF to a pre-bind contingency while the request is drafted. |
76
+ | `hedge bind-submit <submissionId> <bindRequestId> [--attest \| --attest-keys]` | Submit the drafted bind request to Hedge for placement. Same 409 handling as `bind`. |
77
+ | `hedge programs [--category c] [--lob slug] [--state ST]` | The carrier programs your brokerage can quote, one row per program with its category. |
78
+ | `hedge program-schema <programId> [--lob slug] [--state ST]` | The application question schema for an instant-quote program, for your own application builder. |
79
+ | `hedge upload <submissionId> <file.pdf> [--name label]` | Attach an ACORD, loss runs, or supplement. Documents uploaded after create flow into the open lanes. |
80
+ | `hedge requirements <submissionId>` | The gaps view: what the submission still needs (per market, forms, carrier questions). |
81
+ | `hedge finalize <submissionId> [--wait]` | Release a held submission (one created with `--hold`). Idempotent on a submission whose run already started. |
56
82
  | `hedge status <submissionId>` | Submission detail plus the live per-carrier marketing table, status roll-up, and documents on file. |
57
83
  | `hedge submissions [--status state] [--search q] [--limit n] [--offset n] [--updated-since iso]` | List your brokerage's submissions, with filters and paging. |
58
- | `hedge documents <submissionId>` | List a submission's finalized documents (ACORDs, quotes, binders). |
59
- | `hedge download <documentId> [-o file]` | Download a finalized document PDF. |
60
- | `hedge quotes <submissionId>` | List instant-quote carrier sessions and their open questions. |
61
- | `hedge answer <submissionId> <sessionId> --set k=v [--set k=v ...]` | Answer a carrier session's questions. |
62
- | `hedge request-quote <submissionId> <sessionId>` | Close a carrier session, request an indication, then a quote. |
63
- | `hedge policies` | List bound policies with term dates. |
64
- | `hedge policy <policyId>` | Policy detail: term, premium, commission, payment plan, documents. Policy ids come from `hedge policies --json`. |
65
- | `hedge policy-doc <policyId> <kind> [-o file]` | Download a policy PDF; kind is `binder`, `policy`, or `declarations`. |
66
- | `hedge payments` | List payment and invoice status, with invoice links. |
84
+ | `hedge appetite <class> [--state ST] [--lob slug]` | Which markets have appetite for a class of business. |
85
+ | `hedge market-requirements <marketId> --lob <slug> [--state ST] [--programs keys]` | What a market needs to quote a line. Market ids come from `hedge appetite --json`. |
86
+ | `hedge forms [search]` / `hedge form <formKey> [-o file]` | Search the blank application-form catalog and download a blank. |
87
+ | `hedge documents <submissionId>` / `hedge download <documentId> [-o file]` | List a submission's finalized documents and download one. |
88
+ | `hedge quotes <submissionId>` / `hedge answer <submissionId> <sessionId> --set k=v` / `hedge request-quote <submissionId> <sessionId>` | Work an instant-quote carrier session directly. |
89
+ | `hedge policies` / `hedge policy <policyId>` / `hedge policy-doc <policyId> <kind> [-o file]` | Bound policies, one policy's detail, and its `binder`, `policy` or `declarations` PDF. |
90
+ | `hedge payments` | Payment and invoice status, with invoice links. |
67
91
  | `hedge whoami` | Show the signed-in broker and brokerage. |
68
- | `hedge login` / `hedge logout` | Sign in and out. |
92
+ | `hedge login [--browser] [--client-id <id> --client-secret <secret>]` / `hedge logout` | Sign in (device code, browser, or a machine credential) and out. |
69
93
 
70
94
  Add `--json` to any command for the raw API response, so the CLI composes in scripts:
71
95
 
72
96
  ```bash
73
- hedge submissions --json | jq '.[] | select(.status_label=="Quoted") | .insured_name'
97
+ hedge markets <submission-id> --json | jq '.categories[] | select(.category=="instant_quote") | .lanes[] | {market_name, status, quotes}'
74
98
  ```
75
99
 
76
100
  Add `--staging` to any command (or set `HEDGE_ENV=staging`) to target the staging environment.
@@ -79,55 +103,59 @@ Add `--staging` to any command (or set `HEDGE_ENV=staging`) to target the stagin
79
103
 
80
104
  ```bash
81
105
  hedge login
82
- hedge appetite "roofing contractor" --state CA
83
- hedge submit \
84
- --insured "Peak Roofing LLC" \
85
- --narrative "Residential re-roofing, no hot tar, no work over 3 stories" \
86
- --lob commercial_general_liability,commercial_property \
87
- --effective 2026-08-01 \
88
- --website https://peakroofing.example.com \
89
- --fein 12-3456789 \
90
- --entity-type llc \
91
- --naics 238160 \
92
- --business-phone "415-555-0100" \
93
- --business-email office@peakroofing.example.com \
94
- --contact-first Dana --contact-last Rivera \
95
- --contact-email dana@peakroofing.example.com \
96
- --contact-phone "415-555-0101" \
97
- --address "1200 Harrison St" --address2 "Suite 4" \
98
- --city Oakland --state CA --zip 94612 \
99
- --tiv 250000 --payroll 900000 --vehicles 3
100
- hedge upload <submission-id> ./acord-125.pdf --name "ACORD 125"
101
- hedge requirements <submission-id>
102
- hedge finalize <submission-id>
103
- hedge status <submission-id>
104
- hedge documents <submission-id>
105
- hedge download <document-id> -o quote.pdf
106
+ hedge intake ./acord-125.pdf --text "Peak Roofing LLC, residential re-roofing, no hot tar, no work over 3 stories. 12 employees, payroll 900k, 3 vehicles. GL + property, effective 2026-10-01, Oakland CA." --producer-email dana@youragency.com
107
+ hedge markets <submission-id> --wait
108
+ # Hedge Instant Quote: lanes quoted on assumptions ([ ] = still to confirm)
109
+ # Hedge Binding: markets Hedge quotes for you, with needs_from_you
110
+ # Hedge Specialty: email markets waiting on completed ACORDs / loss runs
111
+ hedge answer-asks <submission-id> # list the outstanding asks and their keys
112
+ hedge answer-asks <submission-id> --set years_in_business=8 --set prior_losses=false
113
+ hedge upload <submission-id> ./supplemental.pdf --name "Roofing supplement"
114
+ hedge thread <submission-id> # the clearance email and Hedge's questions
115
+ hedge reply <submission-id> "No hot-tar work; the 3-story limit is firm."
116
+ hedge bind <submission-id> --quote <quote-id> --payment in_full
117
+ # 409: assumptions unconfirmed -> lists each one and the re-run
118
+ hedge bind <submission-id> --quote <quote-id> --payment in_full --attest
119
+ hedge bind-upload <submission-id> <bind-request-id> <contingency-id> ./signed-app.pdf
120
+ hedge bind-submit <submission-id> <bind-request-id>
121
+ hedge bind-status <submission-id> <bind-request-id>
122
+ hedge policies
106
123
  ```
107
124
 
108
- When a mailing address is in play, `--state` fills the mailing address state; the address must be complete (`--address`, `--city`, `--state`, `--zip`, counting fields supplied via `--body`). With `--state` alone it is sent as the submission's top-level `primary_state`.
109
-
110
- Prefer building the request in a file (or another program)? Send a full JSON body. Explicit flags still win: top-level flag values replace matching body keys, applicant flags merge over the body's applicant, and address flags merge into its mailing_address:
125
+ Structured creates still work. `hedge submit` takes the full applicant, contact, address and exposure flags (see `hedge submit --help`), or a JSON body:
111
126
 
112
127
  ```bash
113
128
  hedge submit --body ./submission.json
114
- cat submission.json | hedge submit --body - --effective 2026-09-01
129
+ cat submission.json | hedge submit --body - --effective 2026-09-01 --hold
130
+ hedge finalize <submission-id> --wait # release the held draft
115
131
  ```
116
132
 
117
- Every `submit` sends an `Idempotency-Key` header (a random UUID each run). Replay only engages when the same key is re-sent within 24 hours, so a scripted retry that must not double-create should pass its own key with `--idempotency-key <key>`.
133
+ Every `intake`, `submit` and `reply` sends an `Idempotency-Key` header (a random UUID each run). A scripted retry that must not double-create should pass its own key with `--idempotency-key <key>`.
134
+
135
+ ## Binding and attestation
136
+
137
+ Instant quotes are obtained on recorded assumptions: values Hedge defaulted on the carrier application because the file did not state them. They print on the signable application, so the retail agent must confirm them before the order reaches the carrier. `hedge markets` lists them per lane; `hedge bind` (and `hedge bind-submit`) answer 409 `assumptions_unconfirmed` while any stand, keep the draft, and the CLI prints every statement plus the exact command to confirm:
138
+
139
+ - `--attest` confirms every standing assumption on the quote's carrier application.
140
+ - `--attest-keys k1,k2` confirms only those keys.
141
+
142
+ Send either only after the agent has reviewed each statement; the confirmation is recorded on the bind request under the producing broker. Nothing is sent to a carrier until `hedge bind-submit`.
118
143
 
119
144
  ## Signing in
120
145
 
121
- The CLI signs in with OAuth 2.1, so there are no API keys to copy around for the interactive flow, and it works over SSH.
146
+ The CLI signs in with OAuth 2.1 and works over SSH. Three modes:
122
147
 
123
148
  | Mode | How | When to use |
124
149
  | --- | --- | --- |
125
150
  | Device code (default) | `hedge login` | Anywhere, including headless servers and SSH. Prints a short code and a URL to approve in any browser. |
126
151
  | Browser (loopback) | `hedge login --browser` | A local machine with a browser. Opens it and captures the redirect on `127.0.0.1`. |
152
+ | Machine credential | `hedge login --client-id bac_... --client-secret bas_...` | Scripts and automation. Uses the `client_credentials` grant with an API key created under **Settings → API keys** in the broker portal. Prefer `HEDGE_CLIENT_SECRET` in the environment over the flag. The token is cached with its expiry and renewed by re-exchange; the secret is never printed. |
153
+
154
+ Machine credentials carry no broker identity, so writes made with one must name the producing broker: pass `--producer-email` on `intake`, `submit`, `reply`, `answer-asks`, `withdraw`, `bind`, `bind-upload` and `bind-submit`.
127
155
 
128
156
  Credentials are stored per profile at `~/.config/taven-cli/hedge.<profile>.json` with `0600` permissions. Access tokens are refreshed automatically. Sign out with `hedge logout`.
129
157
 
130
- The CLI requests the `broker_mcp` and `broker_submit` scopes. Submitting on a brokerage's behalf requires that the brokerage has connected apps and programmatic submission enabled by Hedge. Read commands (appetite, market-requirements, submissions, status, requirements, documents, download, quotes, policies, policy, policy-doc, payments) work with either scope. `submit`, `upload`, `finalize`, `answer`, and `request-quote` require `broker_submit`.
158
+ The CLI requests the `broker_mcp` and `broker_submit` scopes. Read commands work with either scope; `intake`, `submit`, `upload`, `finalize`, `reply`, `answer-asks`, `withdraw`, `bind*`, `answer` and `request-quote` require `broker_submit`.
131
159
 
132
160
  ## License
133
161
 
package/dist/index.js CHANGED
@@ -163,6 +163,21 @@ async function loopbackLogin(opts) {
163
163
  let port = 0;
164
164
  });
165
165
  }
166
+ async function clientCredentialsLogin(tokenEndpoint, clientId, clientSecret, scope) {
167
+ const body = new URLSearchParams({ grant_type: "client_credentials", client_id: clientId, client_secret: clientSecret });
168
+ if (scope) body.set("scope", scope);
169
+ const res = await fetch(tokenEndpoint, { method: "POST", headers: FORM, body });
170
+ let tb = { access_token: "" };
171
+ try {
172
+ tb = await res.json();
173
+ } catch {
174
+ }
175
+ if (!res.ok || !tb.access_token) {
176
+ const why = tb.error_description || tb.error || `Token request failed (${res.status})`;
177
+ throw new Error(`Machine credential sign-in failed: ${why}`);
178
+ }
179
+ return tb;
180
+ }
166
181
  async function refresh(tokenEndpoint, clientId, refreshToken) {
167
182
  const res = await fetch(tokenEndpoint, {
168
183
  method: "POST",
@@ -180,11 +195,13 @@ function sleep(ms) {
180
195
  // src/core/http.ts
181
196
  import { writeFileSync as writeFileSync2 } from "fs";
182
197
  var ApiError = class extends Error {
183
- constructor(status, message) {
198
+ constructor(status, message, body) {
184
199
  super(message);
185
200
  this.status = status;
201
+ this.body = body;
186
202
  }
187
203
  status;
204
+ body;
188
205
  };
189
206
  function renderErrorDetail(detail, fallback) {
190
207
  if (detail == null) return fallback;
@@ -210,12 +227,27 @@ function apiErrorFrom(status, parsed, fallback) {
210
227
  } else if (typeof parsed === "string") {
211
228
  detail = parsed;
212
229
  }
213
- return new ApiError(status, renderErrorDetail(detail, fallback));
230
+ return new ApiError(status, renderErrorDetail(detail, fallback), parsed);
214
231
  }
215
232
  async function bearer(cfg) {
216
233
  const tok = loadToken(cfg);
217
234
  if (!tok) throw new ApiError(401, "Not signed in. Run `login` first");
218
235
  if (tok.expires_at - 60 > Math.floor(Date.now() / 1e3)) return tok.access_token;
236
+ if (tok.grant === "client_credentials" && tok.client_secret) {
237
+ try {
238
+ const r = await clientCredentialsLogin(tok.token_endpoint, tok.client_id, tok.client_secret, tok.scope);
239
+ const updated = {
240
+ ...tok,
241
+ access_token: r.access_token,
242
+ expires_at: Math.floor(Date.now() / 1e3) + (r.expires_in ?? 3600),
243
+ scope: r.scope ?? tok.scope
244
+ };
245
+ saveToken(cfg, updated);
246
+ return updated.access_token;
247
+ } catch (e) {
248
+ throw new ApiError(401, `${e instanceof Error ? e.message : String(e)}. Check the key under Settings \u2192 API keys or run \`login --client-id ... --client-secret ...\` again`);
249
+ }
250
+ }
219
251
  if (!tok.refresh_token) throw new ApiError(401, "Session expired. Run `login` again");
220
252
  try {
221
253
  const r = await refresh(tok.token_endpoint, tok.client_id, tok.refresh_token);
@@ -260,9 +292,9 @@ async function apiRequest(opts, method, path, init) {
260
292
  if (!res.ok) throw apiErrorFrom(res.status, parsed, `Request failed (${res.status})`);
261
293
  return parsed;
262
294
  }
263
- async function multipartRequest(opts, method, path, form) {
295
+ async function multipartRequest(opts, method, path, form, extraHeaders) {
264
296
  const url = opts.apiBase.replace(/\/$/, "") + path;
265
- const headers = { Accept: "application/json" };
297
+ const headers = { Accept: "application/json", ...extraHeaders ?? {} };
266
298
  if (opts.apiKey) headers[opts.apiKeyHeader ?? "X-Api-Key"] = opts.apiKey;
267
299
  else headers.Authorization = `Bearer ${await bearer(opts.cfg)}`;
268
300
  const res = await fetch(url, { method, headers, body: form });
@@ -388,6 +420,20 @@ async function loginInteractive(opts) {
388
420
  client_id: clientId
389
421
  });
390
422
  }
423
+ async function loginClientCredentials(opts) {
424
+ const meta = await discover(opts.metadataUrl);
425
+ const tr = await clientCredentialsLogin(meta.token_endpoint, opts.clientId, opts.clientSecret, opts.scope);
426
+ saveToken(opts.cfg, {
427
+ access_token: tr.access_token,
428
+ expires_at: Math.floor(Date.now() / 1e3) + (tr.expires_in ?? 3600),
429
+ scope: tr.scope,
430
+ token_endpoint: meta.token_endpoint,
431
+ client_id: opts.clientId,
432
+ grant: "client_credentials",
433
+ client_secret: opts.clientSecret
434
+ });
435
+ return { scope: tr.scope, expiresIn: tr.expires_in };
436
+ }
391
437
 
392
438
  // src/context.ts
393
439
  function resolveEnv(staging) {
@@ -413,9 +459,33 @@ function makeCtx(opts) {
413
459
  // src/commands/auth.ts
414
460
  var SCOPE = "broker_mcp broker_submit";
415
461
  function registerAuth(program2) {
416
- program2.command("login").description("Sign in to your Hedge broker account").option("--browser", "use the browser (loopback) flow instead of a device code").action(async (opts) => {
462
+ program2.command("login").description("Sign in to your Hedge broker account (device code by default; --client-id for a machine credential)").option("--browser", "use the browser (loopback) flow instead of a device code").option(
463
+ "--client-id <id>",
464
+ "sign in with a brokerage machine credential (client_credentials grant) for scripts and automation; the client id (bac_...) from Settings \u2192 API keys. Pair with --client-secret"
465
+ ).option(
466
+ "--client-secret <secret>",
467
+ "the machine credential's secret (bas_...). Prefer HEDGE_CLIENT_SECRET in the environment so the secret stays out of shell history"
468
+ ).action(async (opts) => {
417
469
  const g = program2.opts();
418
470
  const ctx = makeCtx(g);
471
+ if (opts.clientId || opts.clientSecret) {
472
+ if (opts.browser) throw new Error("--browser and --client-id are different sign-in modes; pass one");
473
+ const clientId = String(opts.clientId ?? "").trim();
474
+ const clientSecret = String(opts.clientSecret ?? process.env.HEDGE_CLIENT_SECRET ?? "").trim();
475
+ if (!clientId) throw new Error("--client-id is required with --client-secret");
476
+ if (!clientSecret) throw new Error("--client-secret (or HEDGE_CLIENT_SECRET) is required with --client-id");
477
+ const r = await loginClientCredentials({
478
+ cfg: ctx.cfg,
479
+ metadataUrl: ctx.env.metadataUrl,
480
+ clientId,
481
+ clientSecret,
482
+ scope: SCOPE
483
+ });
484
+ process.stdout.write(
485
+ `Signed in with machine credential ${clientId}` + (r.scope ? ` (scope: ${r.scope})` : "") + ".\nWrites made with this credential must name the producing broker: pass --producer-email on submit, intake, reply, answer-asks, withdraw and bind.\n"
486
+ );
487
+ return;
488
+ }
419
489
  await loginInteractive({
420
490
  cfg: ctx.cfg,
421
491
  metadataUrl: ctx.env.metadataUrl,
@@ -448,16 +518,227 @@ function registerAuth(program2) {
448
518
  }
449
519
 
450
520
  // src/commands/submissions.ts
521
+ import { randomUUID as randomUUID2 } from "crypto";
522
+ import { readFileSync as readFileSync3 } from "fs";
523
+ import { basename as basename2 } from "path";
524
+
525
+ // src/commands/intake.ts
451
526
  import { randomUUID } from "crypto";
452
527
  import { readFileSync as readFileSync2 } from "fs";
453
528
  import { basename } from "path";
529
+ var CATEGORY_LABELS = {
530
+ instant_quote: "Hedge Instant Quote",
531
+ binding: "Hedge Binding",
532
+ specialty: "Hedge Specialty"
533
+ };
534
+ function nextAfterCreate(submissionId, marketingStatus) {
535
+ if (marketingStatus === "awaiting_finalization") {
536
+ return `Held. Attach documents (hedge upload ${submissionId} <file.pdf>), check hedge requirements ${submissionId}, then release it with hedge finalize ${submissionId}`;
537
+ }
538
+ return `The run started: Hedge is matching appetite and will email the producer the clearance.
539
+ Next: hedge markets ${submissionId} (matching usually lands within ~5 minutes; add --wait to poll)
540
+ hedge thread ${submissionId} (the conversation with Hedge)`;
541
+ }
542
+ function printWarnings(warnings) {
543
+ if (Array.isArray(warnings) && warnings.length) {
544
+ process.stdout.write("\nWarnings:\n" + warnings.map((w) => " - " + String(w)).join("\n") + "\n");
545
+ }
546
+ }
547
+ function readTextSource(source) {
548
+ return source === "-" ? readFileSync2(0, "utf8") : readFileSync2(source, "utf8");
549
+ }
550
+ function fmtAssumption(a) {
551
+ const mark = a.confirmed ? "[x]" : "[ ]";
552
+ const value = a.value === void 0 || a.value === null ? "" : ` = ${typeof a.value === "string" ? a.value : JSON.stringify(a.value)}`;
553
+ return `${mark} ${a.key}: ${a.label ?? ""}${value}`;
554
+ }
555
+ function renderMarkets(view) {
556
+ const out = [];
557
+ for (const group of view.categories ?? []) {
558
+ const label = group.label ?? CATEGORY_LABELS[group.category] ?? group.category;
559
+ out.push(`${label} (${group.category})${group.description ? " - " + group.description : ""}`);
560
+ const lanes = group.lanes ?? [];
561
+ if (!lanes.length) {
562
+ out.push(" (no lanes)");
563
+ out.push("");
564
+ continue;
565
+ }
566
+ const rows = lanes.map((l) => ({
567
+ lane: l.lane_id,
568
+ market: l.market_name ?? "",
569
+ program: l.program ?? "",
570
+ lines: (l.lines || []).join(","),
571
+ status: l.status_label ?? l.status,
572
+ ready: l.submit_ready ? "yes" : "no",
573
+ quote: (l.quotes || []).map((q) => q.premium).join(" / "),
574
+ bound: l.bound ? "yes" : ""
575
+ }));
576
+ out.push(table(rows, ["lane", "market", "program", "lines", "status", "ready", "quote", "bound"]).split("\n").map((x) => " " + x).join("\n"));
577
+ for (const l of lanes) {
578
+ const detail = [];
579
+ if (l.needs_from_you?.length) detail.push(" needs from you: " + l.needs_from_you.join("; "));
580
+ if (l.assumptions?.length) {
581
+ detail.push(" assumptions (pre-bind attestations; [ ] = still to confirm):");
582
+ for (const a of l.assumptions) detail.push(" " + fmtAssumption(a));
583
+ }
584
+ if (l.quotes?.length) {
585
+ for (const q of l.quotes) {
586
+ detail.push(` quote ${q.quote_id}: ${q.premium}${q.lines?.length ? " (" + q.lines.join(", ") + ")" : ""}${q.is_revision ? " [revision]" : ""}`);
587
+ }
588
+ }
589
+ if (detail.length) out.push(` ${l.market_name ?? l.lane_id}${l.program ? " / " + l.program : ""}:
590
+ ` + detail.join("\n"));
591
+ }
592
+ out.push("");
593
+ }
594
+ return out.join("\n").trimEnd();
595
+ }
596
+ function laneCount(view) {
597
+ return (view?.categories ?? []).reduce((n, g) => n + (g.lanes?.length ?? 0), 0);
598
+ }
599
+ function registerIntake(program2) {
600
+ program2.command("intake [files...]").description(
601
+ "Send Hedge a risk the way you would email it: free text and/or PDFs (ACORDs, loss runs, supplements). Creates the submission and STARTS the run unless --hold. Hedge reads the insured out of the text when --insured is omitted"
602
+ ).option("--text <text>", "the risk as you would describe it in an email (required unless --insured or --text-file)").option("--text-file <path|->", "read the text from a file, or - for stdin").option("--insured <name>", "insured business name (skips the extractor for the name)").option("--lob <slugs>", "comma-separated lines of business: canonical slugs or common aliases, e.g. gl,property").option("--state <ST>", "primary risk state, 2-letter").option("--effective <date>", "effective date, YYYY-MM-DD").option("--producer-email <email>", "producing broker to attribute (required with a machine credential)").option("--hold", "keep the draft; release it later with hedge finalize <id>").option(
603
+ "--idempotency-key <key>",
604
+ "Idempotency-Key header value (default: a random UUID per invocation). Re-send the same key to replay the saved submission instead of creating a duplicate"
605
+ ).action(async (files, opts) => {
606
+ const ctx = makeCtx(program2.opts());
607
+ if (opts.text && opts.textFile) throw new Error("pass --text or --text-file, not both");
608
+ const text = opts.textFile ? readTextSource(opts.textFile) : opts.text;
609
+ if (!text?.trim() && !opts.insured) throw new Error("--text (or --text-file) or --insured is required");
610
+ if (files.length > 20) throw new Error(`up to 20 files per intake (got ${files.length})`);
611
+ const form = new FormData();
612
+ if (text?.trim()) form.append("text", text);
613
+ for (const f of files) {
614
+ const bytes = readFileSync2(f);
615
+ if (bytes.byteLength > 15 * 1024 * 1024) throw new Error(`${f} is over the 15 MB per-file limit`);
616
+ form.append("files", new Blob([bytes], { type: "application/pdf" }), basename(f));
617
+ }
618
+ if (opts.insured) form.append("insured_name", opts.insured);
619
+ if (opts.effective) form.append("effective_date", opts.effective);
620
+ if (opts.lob) form.append("lines_of_business", String(opts.lob).split(",").map((s) => s.trim()).filter(Boolean).join(","));
621
+ if (opts.state) form.append("primary_state", String(opts.state).trim().toUpperCase());
622
+ if (opts.producerEmail) form.append("producer_email", opts.producerEmail);
623
+ if (opts.hold) form.append("hold", "true");
624
+ const res = await multipartRequest(ctx.client, "POST", "/broker/intake", form, {
625
+ "Idempotency-Key": opts.idempotencyKey ?? randomUUID()
626
+ });
627
+ if (ctx.json) return printJson(res);
628
+ const applicant = res.applicant ?? {};
629
+ process.stdout.write(kv({
630
+ submission_id: res.submission_id,
631
+ insured: applicant.insured_name ?? "",
632
+ status: res.status_label,
633
+ marketing: res.marketing_status,
634
+ documents: (res.documents || []).map((d) => d.display_name).join(", "),
635
+ portal: res.portal_url
636
+ }) + "\n");
637
+ printWarnings(res.warnings);
638
+ process.stdout.write("\n" + nextAfterCreate(String(res.submission_id), res.marketing_status) + "\n");
639
+ });
640
+ program2.command("markets <submissionId>").description("The markets being tried, in the three Hedge categories (Instant Quote, Binding, Specialty): status, what each lane needs from you, the assumptions Hedge made, released quotes").option("--wait", "poll until at least one lane attaches (usually ~5 minutes after create)").option("--timeout <minutes>", "how long --wait polls before giving up", "8").action(async (submissionId, opts) => {
641
+ const ctx = makeCtx(program2.opts());
642
+ const path = `/broker/submissions/${submissionId}/markets`;
643
+ let view = await apiRequest(ctx.client, "GET", path);
644
+ if (opts.wait && laneCount(view) === 0) {
645
+ const timeoutMin = Number.parseInt(String(opts.timeout), 10);
646
+ if (!Number.isFinite(timeoutMin) || timeoutMin < 1) throw new Error("--timeout expects a whole number of minutes");
647
+ const deadline = Date.now() + timeoutMin * 6e4;
648
+ if (!ctx.json) process.stdout.write("Waiting for markets to match (usually ~5 minutes)");
649
+ while (Date.now() < deadline && laneCount(view) === 0) {
650
+ await new Promise((r) => setTimeout(r, 15e3));
651
+ if (!ctx.json) process.stdout.write(".");
652
+ view = await apiRequest(ctx.client, "GET", path);
653
+ }
654
+ if (!ctx.json) process.stdout.write("\n");
655
+ }
656
+ if (ctx.json) return printJson(view);
657
+ if (laneCount(view) === 0) {
658
+ process.stdout.write(
659
+ renderMarkets(view) + `
660
+
661
+ No lanes yet. Matching usually lands within ~5 minutes of create; an empty view during matching is not "no appetite". If the submission is held, release it with hedge finalize ${submissionId}.
662
+ `
663
+ );
664
+ return;
665
+ }
666
+ process.stdout.write(renderMarkets(view) + "\n");
667
+ process.stdout.write(`
668
+ Bind a released quote: hedge bind ${submissionId} --quote <quoteId> Reply to Hedge: hedge reply ${submissionId} "..."
669
+ `);
670
+ });
671
+ program2.command("answer-asks <submissionId>").description("Answer the outstanding items the matched markets still need, in one batch (keys from the list this prints when run without --set)").option("--set <kv...>", "key=value pairs; values are typed per the item's input (number, bool, select option)").option("--producer-email <email>", "producing broker to attribute (required with a machine credential)").action(async (submissionId, opts) => {
672
+ const ctx = makeCtx(program2.opts());
673
+ const view = await apiRequest(ctx.client, "GET", `/broker/submissions/${submissionId}/outstanding-requirements`);
674
+ const items = view.items ?? [];
675
+ if (!opts.set?.length) {
676
+ if (ctx.json) return printJson(view);
677
+ if (!items.length) {
678
+ process.stdout.write("Nothing outstanding for the matched markets.\n");
679
+ return;
680
+ }
681
+ process.stdout.write(table(items.map((i) => ({
682
+ key: i.key,
683
+ label: i.label,
684
+ input: i.input ?? "",
685
+ options: (i.options || []).map((o) => o.value).join("|"),
686
+ markets: (i.markets || []).join("; ")
687
+ })), ["key", "label", "input", "options", "markets"]) + "\n");
688
+ process.stdout.write(`
689
+ Answer with: hedge answer-asks ${submissionId} --set key=value --set key2=value2
690
+ `);
691
+ return;
692
+ }
693
+ const inputs = new Map(items.map((i) => [String(i.key), String(i.input ?? "")]));
694
+ const answers = [];
695
+ for (const pair of opts.set) {
696
+ const i = String(pair).indexOf("=");
697
+ if (i <= 0) throw new Error(`--set expects key=value, got "${pair}"`);
698
+ const key = String(pair).slice(0, i);
699
+ const raw = String(pair).slice(i + 1);
700
+ const input = inputs.get(key) ?? "";
701
+ let value = raw;
702
+ if (/^(number|integer|currency|percent)$/i.test(input)) {
703
+ const n = Number(raw.replace(/[,$%\s]/g, ""));
704
+ if (!Number.isFinite(n)) throw new Error(`${key} expects a number, got "${raw}"`);
705
+ value = n;
706
+ } else if (/^(bool|boolean|checkbox|yes_no)$/i.test(input)) {
707
+ if (/^(true|yes|y|1)$/i.test(raw)) value = true;
708
+ else if (/^(false|no|n|0)$/i.test(raw)) value = false;
709
+ else throw new Error(`${key} expects yes/no, got "${raw}"`);
710
+ }
711
+ answers.push({ key, value });
712
+ }
713
+ const body = { answers };
714
+ if (opts.producerEmail) body.producer_email = opts.producerEmail;
715
+ const res = await apiRequest(ctx.client, "POST", `/broker/submissions/${submissionId}/outstanding-requirements/answers`, { body });
716
+ if (ctx.json) return printJson(res);
717
+ process.stdout.write(`applied: ${(res.applied || []).join(", ") || "(none)"}
718
+ `);
719
+ for (const s of res.skipped || []) process.stdout.write(`skipped: ${s.key} - ${s.reason}
720
+ `);
721
+ if (res.routed) process.stdout.write(`routed to ${res.routed} carrier session(s)
722
+ `);
723
+ });
724
+ program2.command("withdraw <submissionId>").description("Withdraw a submission: a held draft is deleted; a live placement is pulled from every engaged market and closed as withdrawn").option("--producer-email <email>", "producing broker to attribute (required with a machine credential)").action(async (submissionId, opts) => {
725
+ const ctx = makeCtx(program2.opts());
726
+ const res = await apiRequest(ctx.client, "POST", `/broker/submissions/${submissionId}/withdraw`, {
727
+ body: opts.producerEmail ? { producer_email: opts.producerEmail } : void 0
728
+ });
729
+ if (ctx.json) return printJson(res);
730
+ process.stdout.write(kv({ outcome: res.outcome, detail: res.detail }) + "\n");
731
+ });
732
+ }
733
+
734
+ // src/commands/submissions.ts
454
735
  var POLICY_DOC_KINDS = ["binder", "policy", "declarations"];
455
736
  function intFlag(value, flag) {
456
737
  if (!/^\d+$/.test(String(value).trim())) throw new Error(`${flag} expects a whole number, got "${value}"`);
457
738
  return Number.parseInt(String(value).trim(), 10);
458
739
  }
459
740
  function readJsonBody(source) {
460
- const raw = source === "-" ? readFileSync2(0, "utf8") : readFileSync2(source, "utf8");
741
+ const raw = source === "-" ? readFileSync3(0, "utf8") : readFileSync3(source, "utf8");
461
742
  let parsed;
462
743
  try {
463
744
  parsed = JSON.parse(raw);
@@ -475,7 +756,9 @@ function formatBytes(size) {
475
756
  return `${(n / (1024 * 1024)).toFixed(1)} MB`;
476
757
  }
477
758
  function registerSubmissions(program2) {
478
- program2.command("submit").description("Create a submission (does not market it; run `finalize` when ready)").option("--insured <name>", "insured business name (required unless --body supplies applicant.insured_name)").option("--narrative <text>", "operations description of the risk (required unless --body supplies narrative)").option("--lob <slugs>", "comma-separated lines of business, e.g. commercial_general_liability,workers_compensation").option("--effective <date>", "effective date, YYYY-MM-DD").option("--state <ST>", "primary state (2-letter); with any address flag it becomes the mailing address state instead").option("--website <url>", "insured website").option("--fein <id>", "FEIN (or SSN for a sole proprietor)").option("--entity-type <type>", "legal entity type, e.g. llc, corporation").option("--naics <code>", "NAICS code").option("--business-phone <phone>", "insured business phone").option("--business-email <email>", "insured business email").option("--contact-first <name>", "primary contact first name").option("--contact-last <name>", "primary contact last name").option("--contact-email <email>", "primary contact email").option("--contact-phone <phone>", "primary contact phone").option("--address <line1>", "mailing address line 1").option("--address2 <line2>", "mailing address line 2").option("--city <city>", "mailing address city").option("--zip <zip>", "mailing address ZIP code").option("--tiv <int>", "total insured property value in dollars (property_tiv_total)").option("--vehicles <int>", "number of vehicles (auto_vehicle_count)").option("--payroll <int>", "total annual payroll in dollars (wc_total_annual_payroll)").option("--insured-id <uuid>", "start from an existing insured in your book").option("--producer-email <email>", "producing broker to attribute (required for brokerage API-client credentials)").option(
759
+ program2.command("submit").description(
760
+ "Create a submission from structured fields and START the run: Hedge matches appetite, emails the producer the clearance, opens the lanes and quotes the instant-quote markets. --hold keeps the draft until `hedge finalize`. For free text and PDFs use `hedge intake`"
761
+ ).option("--hold", "defer the run: keep the draft until `hedge finalize <id>` releases it (attach documents and answer requirements first)").option("--insured <name>", "insured business name (required unless --body supplies applicant.insured_name)").option("--narrative <text>", "operations description of the risk (required unless --body supplies narrative)").option("--lob <slugs>", "comma-separated lines of business, e.g. commercial_general_liability,workers_compensation").option("--effective <date>", "effective date, YYYY-MM-DD").option("--state <ST>", "primary state (2-letter); with any address flag it becomes the mailing address state instead").option("--website <url>", "insured website").option("--fein <id>", "FEIN (or SSN for a sole proprietor)").option("--entity-type <type>", "legal entity type, e.g. llc, corporation").option("--naics <code>", "NAICS code").option("--business-phone <phone>", "insured business phone").option("--business-email <email>", "insured business email").option("--contact-first <name>", "primary contact first name").option("--contact-last <name>", "primary contact last name").option("--contact-email <email>", "primary contact email").option("--contact-phone <phone>", "primary contact phone").option("--address <line1>", "mailing address line 1").option("--address2 <line2>", "mailing address line 2").option("--city <city>", "mailing address city").option("--zip <zip>", "mailing address ZIP code").option("--tiv <int>", "total insured property value in dollars (property_tiv_total)").option("--vehicles <int>", "number of vehicles (auto_vehicle_count)").option("--payroll <int>", "total annual payroll in dollars (wc_total_annual_payroll)").option("--insured-id <uuid>", "start from an existing insured in your book").option("--producer-email <email>", "producing broker to attribute (required for brokerage API-client credentials)").option(
479
762
  "--body <file|->",
480
763
  "full JSON request body from a file, or - for stdin. Precedence: flags win; a top-level flag value replaces the matching body key, applicant flags merge over the body's applicant, and address flags merge into its mailing_address"
481
764
  ).option(
@@ -536,6 +819,7 @@ function registerSubmissions(program2) {
536
819
  if (opts.payroll != null) body.wc_total_annual_payroll = intFlag(opts.payroll, "--payroll");
537
820
  if (opts.insuredId) body.insured_id = opts.insuredId;
538
821
  if (opts.producerEmail) body.producer_email = opts.producerEmail;
822
+ if (opts.hold) body.hold = true;
539
823
  const finalApplicant = body.applicant;
540
824
  if (!finalApplicant?.insured_name) throw new Error("--insured is required (or supply applicant.insured_name via --body)");
541
825
  if (!body.narrative) throw new Error("--narrative is required (or supply narrative via --body)");
@@ -544,17 +828,24 @@ function registerSubmissions(program2) {
544
828
  // Random per invocation unless the caller supplies a key. The API
545
829
  // replays the original response when the same key is re-sent within
546
830
  // 24h (per brokerage), so scripted retries should pass their own key.
547
- headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID() }
831
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID2() }
548
832
  });
549
833
  if (ctx.json) return printJson(res);
550
- process.stdout.write(kv({ submission_id: res.submission_id, state: res.state, status: res.status_label }) + "\n");
551
- process.stdout.write("\nNext: hedge upload " + res.submission_id + " <file.pdf>, hedge requirements " + res.submission_id + ", hedge finalize " + res.submission_id + "\n");
834
+ process.stdout.write(kv({
835
+ submission_id: res.submission_id,
836
+ state: res.state,
837
+ status: res.status_label,
838
+ marketing: res.marketing_status ?? "",
839
+ next_step: res.next_step ?? ""
840
+ }) + "\n");
841
+ printWarnings(res.warnings);
842
+ process.stdout.write("\n" + nextAfterCreate(String(res.submission_id), res.marketing_status) + "\n");
552
843
  });
553
844
  program2.command("upload <submissionId> <pdf>").description("Attach a PDF (ACORD, loss runs, supplement) to a submission").option("--name <label>", "display name for the document").action(async (submissionId, pdf, opts) => {
554
845
  const ctx = makeCtx(program2.opts());
555
- const bytes = readFileSync2(pdf);
846
+ const bytes = readFileSync3(pdf);
556
847
  const form = new FormData();
557
- form.append("file", new Blob([bytes], { type: "application/pdf" }), basename(pdf));
848
+ form.append("file", new Blob([bytes], { type: "application/pdf" }), basename2(pdf));
558
849
  if (opts.name) form.append("display_name", opts.name);
559
850
  const res = await multipartRequest(ctx.client, "POST", `/broker/submissions/${submissionId}/documents`, form);
560
851
  if (ctx.json) return printJson(res);
@@ -577,9 +868,9 @@ function registerSubmissions(program2) {
577
868
  } else {
578
869
  const hint = typeof r.marketing_hint === "string" && r.marketing_hint ? r.marketing_hint : null;
579
870
  if (r.marketing_status === "matching") {
580
- process.stdout.write("\nNo markets yet - " + (hint ?? "Hedge is matching carrier markets now (usually ~5 minutes after finalize); re-run this in a few minutes.") + "\n");
871
+ process.stdout.write("\nNo markets yet - " + (hint ?? "Hedge is matching carrier markets now (usually ~5 minutes after create); check hedge markets " + submissionId + " in a few minutes.") + "\n");
581
872
  } else if (r.marketing_status === "not_started") {
582
- process.stdout.write("\nNo markets yet - " + (hint ?? "run hedge finalize " + submissionId + " to start marketing.") + "\n");
873
+ process.stdout.write("\nNo markets yet - " + (hint ?? "this submission is held; run hedge finalize " + submissionId + " to release it.") + "\n");
583
874
  } else if (r.marketing_status === "no_markets_matched") {
584
875
  process.stdout.write("\nNo markets attached - " + (hint ?? "Hedge is reviewing options for this risk and will follow up.") + "\n");
585
876
  } else {
@@ -609,7 +900,7 @@ function registerSubmissions(program2) {
609
900
  quote: l.quote_premium ?? ""
610
901
  }))), ["carrier", "line", "status", "quote"]) + "\n");
611
902
  } else {
612
- process.stdout.write("\nNo markets attached yet. Matching usually completes within ~5 minutes of finalize - re-run this shortly (or check hedge requirements " + submissionId + ").\n");
903
+ process.stdout.write("\nNo markets attached yet. Matching usually completes within ~5 minutes of create - re-run this shortly (or check hedge markets " + submissionId + ").\n");
613
904
  }
614
905
  const byStatus = s.status_summary?.by_status;
615
906
  if (byStatus && Object.keys(byStatus).length) {
@@ -633,7 +924,7 @@ function registerSubmissions(program2) {
633
924
  premium: r.premium ?? ""
634
925
  })), ["id", "insured", "lines", "status", "premium"]) + "\n");
635
926
  });
636
- program2.command("finalize <submissionId>").description("Start marketing the submission to carriers").option("--wait", "poll until markets attach (usually ~5 minutes), then print them").option("--timeout <minutes>", "how long --wait polls before giving up", "8").action(async (submissionId, opts) => {
927
+ program2.command("finalize <submissionId>").description("Release a held submission: start the run for a submission created with --hold (idempotent on one already running)").option("--wait", "poll until markets attach (usually ~5 minutes), then print them").option("--timeout <minutes>", "how long --wait polls before giving up", "8").action(async (submissionId, opts) => {
637
928
  const ctx = makeCtx(program2.opts());
638
929
  const r = await apiRequest(ctx.client, "POST", `/broker/submissions/${submissionId}/finalize`);
639
930
  if (ctx.json && !opts.wait) return printJson(r);
@@ -641,7 +932,7 @@ function registerSubmissions(program2) {
641
932
  const wait = Number(r.typical_wait_seconds) > 0 ? Math.round(Number(r.typical_wait_seconds) / 60) : 5;
642
933
  process.stdout.write(
643
934
  `Marketing started. Markets usually attach within ~${wait} minutes.
644
- Track with: hedge status ${submissionId} (or use finalize --wait next time)
935
+ Track with: hedge markets ${submissionId} (or use finalize --wait next time)
645
936
  `
646
937
  );
647
938
  return;
@@ -691,7 +982,7 @@ Check in with: hedge requirements ${submissionId}
691
982
  if (req.forms?.length) {
692
983
  process.stdout.write("\nForms Hedge prepares: " + req.forms.join(", ") + "\n(download blanks with: hedge form <form_key>)\n");
693
984
  }
694
- process.stdout.write("\nTrack sends and quotes with: hedge status " + submissionId + "\n");
985
+ process.stdout.write("\nThe three market categories, asks, assumptions and quotes: hedge markets " + submissionId + "\n");
695
986
  });
696
987
  program2.command("forms [search]").description('Search the blank application-form catalog, e.g. hedge forms "liquor"').action(async (search) => {
697
988
  const ctx = makeCtx(program2.opts());
@@ -787,6 +1078,305 @@ Check in with: hedge requirements ${submissionId}
787
1078
  });
788
1079
  }
789
1080
 
1081
+ // src/commands/thread.ts
1082
+ import { randomUUID as randomUUID3 } from "crypto";
1083
+ import { readFileSync as readFileSync4 } from "fs";
1084
+ function who(m) {
1085
+ if (m.direction === "outbound") return "Hedge -> you";
1086
+ return "you -> Hedge";
1087
+ }
1088
+ function renderMessage(m) {
1089
+ const when = String(m.occurred_at ?? "").replace("T", " ").slice(0, 16);
1090
+ const head = `-- ${when} ${who(m)} [${m.channel}/${m.kind}]${m.subject ? " " + m.subject : ""}`;
1091
+ const lines = [head];
1092
+ if (m.from) lines.push(` from: ${m.from}${m.to?.length ? " to: " + m.to.join(", ") : ""}`);
1093
+ lines.push(String(m.body_text ?? "").trimEnd());
1094
+ if (m.attachments?.length) {
1095
+ lines.push(" attachments: " + m.attachments.map((a) => a.name + (a.download_path ? ` (${a.download_path})` : "")).join(", "));
1096
+ }
1097
+ return lines.join("\n");
1098
+ }
1099
+ function registerThread(program2) {
1100
+ program2.command("thread <submissionId>").description(
1101
+ "The conversation with Hedge on a submission, oldest first: the clearance email, questions, market-plan updates, quote deliveries, your replies. Follows next_cursor to the tail"
1102
+ ).option("--since <cursor>", "start strictly after this cursor (a next_cursor/last_cursor from an earlier run); omit to read from the beginning").option("--limit <n>", "messages per page, 1-200", "50").option("--one-page", "read a single page instead of following next_cursor to the tail").action(async (submissionId, opts) => {
1103
+ const ctx = makeCtx(program2.opts());
1104
+ const messages = [];
1105
+ let since = opts.since;
1106
+ let lastCursor = opts.since ?? null;
1107
+ let nextCursor = null;
1108
+ for (; ; ) {
1109
+ const page = await apiRequest(ctx.client, "GET", `/broker/submissions/${submissionId}/thread`, {
1110
+ query: { since, limit: String(opts.limit) }
1111
+ });
1112
+ messages.push(...page.messages ?? []);
1113
+ nextCursor = page.next_cursor ?? null;
1114
+ if (nextCursor) lastCursor = nextCursor;
1115
+ if (!nextCursor || opts.onePage) break;
1116
+ since = nextCursor;
1117
+ }
1118
+ if (ctx.json) return printJson({ messages, next_cursor: nextCursor, last_cursor: lastCursor });
1119
+ if (!messages.length) {
1120
+ process.stdout.write("No messages yet. Hedge opens the thread with the clearance email once the run starts.\n");
1121
+ return;
1122
+ }
1123
+ process.stdout.write(messages.map(renderMessage).join("\n\n") + "\n");
1124
+ if (nextCursor) process.stdout.write(`
1125
+ More may follow. Continue with: hedge thread ${submissionId} --since ${nextCursor}
1126
+ `);
1127
+ else if (lastCursor) process.stdout.write(`
1128
+ At the tail. Resume later with: hedge thread ${submissionId} --since ${lastCursor} (dedupe by id)
1129
+ `);
1130
+ process.stdout.write(`Reply with: hedge reply ${submissionId} "..."
1131
+ `);
1132
+ });
1133
+ program2.command("reply <submissionId> <text>").description("Reply to Hedge on a submission exactly as you would reply to its email: answer a question, send a revision, authorize a bind. Use - to read the message from stdin").option("--attach <documentIds>", "comma-separated ids of documents already uploaded to this submission (hedge upload) to attach").option("--producer-email <email>", "producing broker to attribute (required with a machine credential)").option("--idempotency-key <key>", "Idempotency-Key header (default: a random UUID per invocation); a retry with the same key never sends the message twice").action(async (submissionId, text, opts) => {
1134
+ const ctx = makeCtx(program2.opts());
1135
+ const body = text === "-" ? readFileSync4(0, "utf8") : String(text);
1136
+ if (!body.trim()) throw new Error("the reply is empty");
1137
+ if (body.length > 2e4) throw new Error("the reply is over 20000 characters");
1138
+ const payload = { body };
1139
+ if (opts.attach) payload.document_ids = String(opts.attach).split(",").map((s) => s.trim()).filter(Boolean);
1140
+ if (opts.producerEmail) payload.producer_email = opts.producerEmail;
1141
+ const res = await apiRequest(ctx.client, "POST", `/broker/submissions/${submissionId}/thread`, {
1142
+ body: payload,
1143
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID3() }
1144
+ });
1145
+ if (ctx.json) return printJson(res);
1146
+ process.stdout.write(kv({ message_id: res.message_id, status: res.status, detail: res.detail }) + "\n");
1147
+ process.stdout.write(`
1148
+ Hedge answers on the thread, typically within a couple of minutes: hedge thread ${submissionId}
1149
+ `);
1150
+ });
1151
+ }
1152
+
1153
+ // src/commands/bind.ts
1154
+ import { readFileSync as readFileSync5 } from "fs";
1155
+ import { basename as basename3 } from "path";
1156
+ var PAYMENT_OPTIONS = ["in_full", "monthly"];
1157
+ function attestValue(opts) {
1158
+ if (opts.attest && opts.attestKeys) throw new Error("pass --attest or --attest-keys, not both");
1159
+ if (opts.attest) return true;
1160
+ if (opts.attestKeys) {
1161
+ const keys = String(opts.attestKeys).split(",").map((s) => s.trim()).filter(Boolean);
1162
+ if (!keys.length) throw new Error("--attest-keys needs at least one key");
1163
+ return keys;
1164
+ }
1165
+ return false;
1166
+ }
1167
+ function fmtAssumption2(a) {
1168
+ const value = a.value === void 0 || a.value === null ? "" : ` = ${typeof a.value === "string" ? a.value : JSON.stringify(a.value)}`;
1169
+ return ` - ${a.key}: ${a.label ?? ""}${value}`;
1170
+ }
1171
+ function unconfirmedFrom(err) {
1172
+ if (!(err instanceof ApiError) || err.status !== 409) return null;
1173
+ const body = err.body;
1174
+ const detail = body?.detail;
1175
+ if (detail && typeof detail === "object" && detail.code === "assumptions_unconfirmed") return detail;
1176
+ return null;
1177
+ }
1178
+ function printUnconfirmed(detail, howToAttest, json) {
1179
+ if (json) {
1180
+ printJson(detail);
1181
+ } else {
1182
+ process.stdout.write(
1183
+ `Assumptions unconfirmed (bind request ${detail.bind_request_id} kept as a draft; nothing was sent to the carrier).
1184
+
1185
+ Hedge obtained this quote on these assumptions. The retail agent must review each statement and confirm it:
1186
+ ` + (detail.assumptions || []).map(fmtAssumption2).join("\n") + `
1187
+
1188
+ Once reviewed, confirm and continue with:
1189
+ ${howToAttest} --attest
1190
+ ${howToAttest} --attest-keys ${(detail.assumptions || []).map((a) => a.key).join(",") || "<key,key>"} (only the keys the agent confirmed)
1191
+ `
1192
+ );
1193
+ }
1194
+ process.exitCode = 1;
1195
+ }
1196
+ function printBindRequest(r) {
1197
+ process.stdout.write(kv({
1198
+ bind_request_id: r.bind_request_id,
1199
+ status: `${r.status}${r.status_label ? " (" + r.status_label + ")" : ""}`,
1200
+ quote_id: r.quote_id ?? "",
1201
+ payment: r.payment_option ?? "",
1202
+ requested_at: r.requested_at ?? "",
1203
+ requested_by: r.requested_by ?? "",
1204
+ effective: r.effective_date ?? "",
1205
+ policy_id: r.policy_id ?? ""
1206
+ }) + "\n");
1207
+ if (r.unconfirmed_assumptions?.length) {
1208
+ process.stdout.write("\nUnconfirmed assumptions (create/submit answer 409 until attested):\n" + r.unconfirmed_assumptions.map(fmtAssumption2).join("\n") + "\n");
1209
+ }
1210
+ if (r.contingencies?.length) {
1211
+ process.stdout.write("\nContingencies:\n" + table(r.contingencies.map((c) => ({
1212
+ id: c.id,
1213
+ kind: c.kind,
1214
+ label: c.label,
1215
+ status: c.status,
1216
+ required: c.required ? "yes" : "no",
1217
+ esign: c.esign_status ?? "",
1218
+ upload: c.upload_path ? "hedge bind-upload ... " + c.id + " <file.pdf>" : ""
1219
+ })), ["id", "kind", "label", "status", "required", "esign", "upload"]) + "\n");
1220
+ }
1221
+ if (r.next_steps?.length) {
1222
+ process.stdout.write("\nNext steps:\n" + r.next_steps.map((s) => " - " + s).join("\n") + "\n");
1223
+ }
1224
+ }
1225
+ function registerBind(program2) {
1226
+ program2.command("bind <submissionId>").description(
1227
+ "Request to bind a released quote (the broker's bind authority). Drafts the bind request with one slot per carrier contingency; answers 409 with the assumptions to attest while any stand unconfirmed"
1228
+ ).requiredOption("--quote <quoteId>", "the released quote to bind: a quote_id from hedge markets (indications cannot be bound)").option("--payment <option>", `payment plan: ${PAYMENT_OPTIONS.join(" or ")}`, "in_full").option("--attest", "confirm EVERY standing assumption on the quote's carrier application. Only after the retail agent has reviewed each statement; the confirmation is recorded under the producing broker").option("--attest-keys <keys>", "confirm only these assumption keys, comma-separated").option("--producer-email <email>", "producing broker to attribute (required with a machine credential)").action(async (submissionId, opts) => {
1229
+ if (!PAYMENT_OPTIONS.includes(opts.payment)) throw new Error(`--payment must be ${PAYMENT_OPTIONS.join(" or ")}`);
1230
+ const ctx = makeCtx(program2.opts());
1231
+ const body = { quote_id: opts.quote, payment_option: opts.payment, attest_assumptions: attestValue(opts) };
1232
+ if (opts.producerEmail) body.producer_email = opts.producerEmail;
1233
+ let r;
1234
+ try {
1235
+ r = await apiRequest(ctx.client, "POST", `/broker/submissions/${submissionId}/bind-requests`, { body });
1236
+ } catch (err) {
1237
+ const detail = unconfirmedFrom(err);
1238
+ if (!detail) throw err;
1239
+ printUnconfirmed(detail, `hedge bind ${submissionId} --quote ${opts.quote} --payment ${opts.payment}${opts.producerEmail ? " --producer-email " + opts.producerEmail : ""}`, ctx.json);
1240
+ return;
1241
+ }
1242
+ if (ctx.json) return printJson(r);
1243
+ printBindRequest(r);
1244
+ if (r.status === "drafted") {
1245
+ process.stdout.write(
1246
+ `
1247
+ Upload contingency documents with hedge bind-upload ${submissionId} ${r.bind_request_id} <contingencyId> <file.pdf>, then send it to Hedge for placement: hedge bind-submit ${submissionId} ${r.bind_request_id}
1248
+ `
1249
+ );
1250
+ }
1251
+ });
1252
+ program2.command("bind-status <submissionId> [bindRequestId]").description("Bind requests on a submission (newest first), or one request in full: status, unconfirmed assumptions, contingencies, next steps").action(async (submissionId, bindRequestId) => {
1253
+ const ctx = makeCtx(program2.opts());
1254
+ if (bindRequestId) {
1255
+ const r = await apiRequest(ctx.client, "GET", `/broker/submissions/${submissionId}/bind-requests/${bindRequestId}`);
1256
+ if (ctx.json) return printJson(r);
1257
+ printBindRequest(r);
1258
+ return;
1259
+ }
1260
+ const rows = await apiRequest(ctx.client, "GET", `/broker/submissions/${submissionId}/bind-requests`);
1261
+ if (ctx.json) return printJson(rows);
1262
+ if (!rows.length) {
1263
+ process.stdout.write(`No bind requests yet. Start one with: hedge bind ${submissionId} --quote <quoteId>
1264
+ `);
1265
+ return;
1266
+ }
1267
+ process.stdout.write(table(rows.map((r) => ({
1268
+ bind_request_id: r.bind_request_id,
1269
+ status: r.status_label ?? r.status,
1270
+ quote_id: r.quote_id ?? "",
1271
+ requested_at: String(r.requested_at ?? "").slice(0, 16).replace("T", " "),
1272
+ unconfirmed: (r.unconfirmed_assumptions || []).length,
1273
+ pending: (r.contingencies || []).filter((c) => c.status === "pending").length,
1274
+ policy_id: r.policy_id ?? ""
1275
+ })), ["bind_request_id", "status", "quote_id", "requested_at", "unconfirmed", "pending", "policy_id"]) + "\n");
1276
+ process.stdout.write(`
1277
+ Detail: hedge bind-status ${submissionId} <bindRequestId>
1278
+ `);
1279
+ });
1280
+ program2.command("bind-upload <submissionId> <bindRequestId> <contingencyId> <pdf>").description("Attach a PDF to a pre-bind contingency (accepted while the request is drafted); marks the item satisfied").option("--notes <text>", "notes for the underwriter").option("--producer-email <email>", "producing broker to attribute (required with a machine credential)").action(async (submissionId, bindRequestId, contingencyId, pdf, opts) => {
1281
+ const ctx = makeCtx(program2.opts());
1282
+ const form = new FormData();
1283
+ form.append("file", new Blob([readFileSync5(pdf)], { type: "application/pdf" }), basename3(pdf));
1284
+ if (opts.notes) form.append("notes", opts.notes);
1285
+ if (opts.producerEmail) form.append("producer_email", opts.producerEmail);
1286
+ const r = await multipartRequest(
1287
+ ctx.client,
1288
+ "POST",
1289
+ `/broker/submissions/${submissionId}/bind-requests/${bindRequestId}/contingencies/${contingencyId}/document`,
1290
+ form
1291
+ );
1292
+ if (ctx.json) return printJson(r);
1293
+ printBindRequest(r);
1294
+ });
1295
+ program2.command("bind-submit <submissionId> <bindRequestId>").description("Submit the drafted bind request to Hedge for placement with the carrier; answers 409 with the assumptions to attest while any stand unconfirmed").option("--attest", "confirm EVERY standing assumption and submit in one call (only after the retail agent has reviewed each statement)").option("--attest-keys <keys>", "confirm only these assumption keys, comma-separated").option("--producer-email <email>", "producing broker to attribute (required with a machine credential)").action(async (submissionId, bindRequestId, opts) => {
1296
+ const ctx = makeCtx(program2.opts());
1297
+ const attest = attestValue(opts);
1298
+ const body = {};
1299
+ if (attest !== false) body.attest_assumptions = attest;
1300
+ if (opts.producerEmail) body.producer_email = opts.producerEmail;
1301
+ let r;
1302
+ try {
1303
+ r = await apiRequest(ctx.client, "POST", `/broker/submissions/${submissionId}/bind-requests/${bindRequestId}/submit`, {
1304
+ body: Object.keys(body).length ? body : void 0
1305
+ });
1306
+ } catch (err) {
1307
+ const detail = unconfirmedFrom(err);
1308
+ if (!detail) throw err;
1309
+ printUnconfirmed(detail, `hedge bind-submit ${submissionId} ${bindRequestId}${opts.producerEmail ? " --producer-email " + opts.producerEmail : ""}`, ctx.json);
1310
+ return;
1311
+ }
1312
+ if (ctx.json) return printJson(r);
1313
+ printBindRequest(r);
1314
+ process.stdout.write(`
1315
+ Hedge is placing the bind. Track it with hedge bind-status ${submissionId} ${bindRequestId}; a bound event follows on the events feed and webhooks.
1316
+ `);
1317
+ });
1318
+ }
1319
+
1320
+ // src/commands/programs.ts
1321
+ var CATEGORIES = ["instant_quote", "binding", "specialty"];
1322
+ function renderQuestions(questions, depth) {
1323
+ const pad = " ".repeat(depth);
1324
+ const out = [];
1325
+ for (const q of questions ?? []) {
1326
+ const flags = [q.required ? "required" : "", q.repeatable ? "repeatable" : "", q.depends_on?.length ? `depends on ${q.depends_on.join(",")}` : ""].filter(Boolean);
1327
+ const options = q.options?.length ? " options: " + q.options.map((o) => o.value === void 0 ? o.label : o.value).join("|") : "";
1328
+ out.push(`${pad}${q.key} [${q.type}${flags.length ? "; " + flags.join("; ") : ""}] ${q.label ?? ""}${options}`);
1329
+ if (q.help) out.push(`${pad} ${q.help}`);
1330
+ if (q.questions?.length) out.push(...renderQuestions(q.questions, depth + 1));
1331
+ }
1332
+ return out;
1333
+ }
1334
+ function registerPrograms(program2) {
1335
+ program2.command("programs").description("The carrier programs your brokerage can quote through Hedge, one row per program with its category (instant_quote, binding, specialty)").option("--category <category>", `filter: ${CATEGORIES.join(", ")}`).option("--lob <slug>", "filter by line of business (canonical slug or alias, e.g. gl)").option("--state <ST>", "filter by 2-letter state").action(async (opts) => {
1336
+ if (opts.category && !CATEGORIES.includes(opts.category)) throw new Error(`--category must be one of ${CATEGORIES.join(", ")}`);
1337
+ const ctx = makeCtx(program2.opts());
1338
+ const rows = await apiRequest(ctx.client, "GET", "/broker/programs", {
1339
+ query: { category: opts.category, lob: opts.lob, state: opts.state ? String(opts.state).toUpperCase() : void 0 }
1340
+ });
1341
+ if (ctx.json) return printJson(rows);
1342
+ process.stdout.write(table(rows.map((p) => ({
1343
+ program_id: p.program_id,
1344
+ market: p.market_name,
1345
+ program: p.name,
1346
+ category: p.program_category ?? "",
1347
+ lines: (p.lines || []).join(","),
1348
+ states: p.states == null ? "all" : p.states.join(","),
1349
+ enabled: p.enabled ? "yes" : "no",
1350
+ schema: p.application_schema_available ? "yes" : ""
1351
+ })), ["program_id", "market", "program", "category", "lines", "states", "enabled", "schema"]) + "\n");
1352
+ process.stdout.write("\nQuestions an instant-quote program asks: hedge program-schema <programId>\n");
1353
+ });
1354
+ program2.command("program-schema <programId>").description("The application question schema for an instant-quote program: sections of questions with stable keys, types, options and dependencies, for your own application builder").option("--lob <slug>", "check the program lists this line").option("--state <ST>", "check the program is available in this state").action(async (programId, opts) => {
1355
+ const ctx = makeCtx(program2.opts());
1356
+ const s = await apiRequest(ctx.client, "GET", `/broker/programs/${programId}/application-schema`, {
1357
+ query: { lob: opts.lob, state: opts.state ? String(opts.state).toUpperCase() : void 0 }
1358
+ });
1359
+ if (ctx.json) return printJson(s);
1360
+ process.stdout.write(kv({
1361
+ program: s.name,
1362
+ program_id: s.program_id,
1363
+ market_id: s.market_id,
1364
+ category: s.program_category,
1365
+ contract: s.contract_version,
1366
+ fingerprint: s.schema_fingerprint,
1367
+ title: s.title ?? ""
1368
+ }) + "\n");
1369
+ if (s.description) process.stdout.write(s.description + "\n");
1370
+ for (const section of s.sections ?? []) {
1371
+ process.stdout.write(`
1372
+ ## ${section.label} (${section.key})
1373
+ `);
1374
+ process.stdout.write(renderQuestions(section.questions, 1).join("\n") + "\n");
1375
+ }
1376
+ process.stdout.write("\nSave answers against these keys with the application-workspace checkpoint (see docs.hedgespecialty.com).\n");
1377
+ });
1378
+ }
1379
+
790
1380
  // src/commands/appetite.ts
791
1381
  function requirementRows(items) {
792
1382
  return items.map((q) => ({
@@ -887,9 +1477,13 @@ function registerQuotes(program2) {
887
1477
 
888
1478
  // src/index.ts
889
1479
  var program = new Command();
890
- program.name("hedge").description("Submit risks to Hedge and track them from your terminal.").version("0.3.2").option("--staging", "use the staging environment").option("--json", "output raw JSON (for scripting)");
1480
+ program.name("hedge").description("Send Hedge a risk from your terminal and track it to bind: intake, markets, thread, bind.").version("0.4.0").option("--staging", "use the staging environment").option("--json", "output raw JSON (for scripting)");
891
1481
  registerAuth(program);
1482
+ registerIntake(program);
892
1483
  registerSubmissions(program);
1484
+ registerThread(program);
1485
+ registerBind(program);
1486
+ registerPrograms(program);
893
1487
  registerAppetite(program);
894
1488
  registerQuotes(program);
895
1489
  program.hook("preAction", () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hedge-broker",
3
- "version": "0.3.2",
4
- "description": "Command-line tool for the Hedge broker portal. Submit risks, check appetite, track quotes and policies.",
3
+ "version": "0.4.0",
4
+ "description": "Command-line tool for Hedge. Send a risk (text and PDFs), read the markets Hedge is trying, follow the thread, and bind, from your terminal or a script.",
5
5
  "type": "module",
6
6
  "bin": { "hedge": "dist/index.js" },
7
7
  "files": ["dist", "README.md", "CHANGELOG.md"],