---
name: eximagent
description: Trade-intelligence CLI for coding agents. Buyer/importer/distributor discovery, B2B lead gen, contact enrichment, cold outreach with stage tracking, tariff + HS-code + corridor + country lookups, OFAC sanctions screening, per-company negotiation memory, company-profile extraction from any website, raw-markdown crawl, product/facility image extraction for multimodal hosts, bulk processing with auto-pick. Use whenever user mentions eximagent/this CLI/the trade CLI, or asks: find buyers / importers / distributors, look up tariffs, what HS code, sanctions check, draft cold outreach, what trade duties apply, tell me about this company website, enrich N companies, crawl these websites, show/list/manage my saved collections or corridors or templates or knowledge base, my trade prospect lists, or any export/import/international-trade task. Invoke `eximagent <cmd>` for every step; auth via OAuth device flow or PAT.
---

# eximagent — trade-domain CLI for coding agents

## Install

If the `eximagent` binary is not already on PATH, install it first — one command, no runtime to set up.

**macOS / Linux:**

```bash
curl -fsSL https://cli.eximagent.ai/install | sh
```

**Windows (PowerShell):**

```powershell
irm https://cli.eximagent.ai/install.ps1 | iex
```

Then authenticate with `eximagent login` (OAuth device flow) or `eximagent login --token <PAT>`, and verify with `eximagent whoami`. The installer also drops this skill into the host agent's skill directories.

## Quick start

1. `eximagent whoami` — confirm auth (run `eximagent login` if it fails).
2. `eximagent profile get` — see the operator profile that grounds every later turn.
3. `eximagent hscode search --query "<product>"` — disambiguate HS code if the user did not give one.
4. `eximagent search run --product "<product>" --location <country>` (preview) → `--confirmed` to start.
5. `eximagent collection get --name <name>` → `eximagent enrich company --canonicalUrl <url>` for the standouts.
6. `eximagent enrich contacts --collectionId <id>` (filter-free first pass) → `eximagent email draft --dry-run` → confirm → `eximagent email send --confirm`.
7. Stuck? `eximagent collection analyze --collectionId <id> --question "<plain English>"` ranks with reasoning.

## Intent map — plain-language user phrase to canonical command

| User says | Canonical command | Why |
|-----------|-------------------|-----|
| "find buyers / importers / distributors" | `search run` | discovery pipeline |
| "help me understand this company" | `enrich company` | per-company deep crawl + summary |
| "find employee contacts" / "find decision makers" | `enrich contacts` | filter-free people search; recall-first |
| "find procurement people" | `enrich contacts` then `employees filter --departments procurement` | discover wide, narrow after |
| "rank these by size / fit / strategic priority" | `collection analyze --question "..."` | LLM ranking with auditable evidence per rank |
| "re-check this person's email" | `enrich contact --employeeId <id>` | single-row re-verify |
| "look up duties / tariff" | `tariff` first, `trade lookup --type ...` fallback | structured trade DB |
| "what HS code matches X" | `hscode search --query X` | HS disambiguation |
| "screen against sanctions" | `sanctions check --name X` | OFAC SDN |
| "save this trade lane" | `corridor save` | reusable corridor |
| "draft outreach to these" | `email draft --dry-run` | preview-first |
| "send all" | `email send --confirm` | flush with countdown |
| "show / list / manage my saved <kind>" | `<kind> list` (server-side) | NEVER search local files |

Read past this point only when the quick-start chain leaves a question unanswered. The deeper material below explains every command, doctrine, error code, stream protocol, and workflow-state primitive.

You have `eximagent`, a CLI of trade-domain primitives. Use it for buyer discovery, trade research, company enrichment, contact enrichment, outreach drafting, sanctions checks, corridor management, raw-page crawls, image-rich multimodal outputs, and trade-memory tasks.

You are the orchestrator. `eximagent` is a dispatcher, not an autonomous workflow engine. Each command does one thing; your job is to choose the right order, batch by default, preview expensive steps, and keep the user out of bad runs.

## What this CLI is best at

- Fast first-pass buyer discovery from a vague prompt
- HS code and tariff research
- Building collections of trade prospects
- Lightweight company-level enrichment from websites (structured + image-bearing)
- Raw-markdown crawl of any website with caching
- Bulk processing of large lists (companies, URLs, HS queries) in a single call
- Drafting outreach once the target list is already clean

## What this CLI is not good at by itself

- Fully trustworthy contact data — you must respect confidence tags
- Clean send-ready outreach lists without review
- Resolving exact LinkedIn company URLs from raw company names at scale (auto-resolve helps; not perfect)
- Anything outside the trade-domain surface

Treat `eximagent` as a strong discovery and enrichment helper. The data it returns is honest about its own confidence — read the tags and speak about each level accurately.

## Golden path

Use this order unless the user explicitly wants something else:

1. Ground in auth and profile
2. Clarify product, market, and HS code if needed
3. Run buyer search (preview → confirm → stream)
4. Inspect results and shortlist
5. Enrich company data on the shortlist (`enrich company`)
6. Enrich contacts only for validated targets (`enrich contacts`)
7. Draft outreach (`email draft --dry-run` first)

Skipping the shortlist step is the most common waste of budget and time.

## Decision tree — if the user asks X, do Y

- **Find buyers / importers / distributors** → `profile get` to ground → clarify product + market + HS → `hscode search` if HS unknown → `--dry-run search run` preview → confirm → `--stream search run --confirmed`
- **Look up tariff** → if HS is known: `tariff --exporter --importer --product`; otherwise `hscode search` first
- **Custom duties / NTM measures / remedies** → `trade lookup --type duties|taxes|remedies|ntm|all`
- **Identify a company from a name** → `company --name "..."` (single) or `--inputs file.ndjson` (bulk)
- **Profile a website** → `enrich company --url ...` (single) or `--inputs file.ndjson` (bulk). For just raw markdown without structuring: `crawl --url ...`
- **Screen against sanctions** → `sanctions check --name "..."` or `--inputs names.ndjson`
- **Draft outreach** → verify list quality first, then `email draft --dry-run` → user confirms → `email send --confirm`
- **Show me / my / saved / existing collections|corridors|templates|kb** → these are SERVER-side, owned by the authenticated user. NEVER search the local filesystem, session state, or temp dirs for them. Call the command directly: `collection list` / `corridor list` / `template list` / `kb list`. "saved"/"my"/"existing" never means local files.
- **Multimodal "what do these companies actually sell?"** → bulk `enrich company` for the list → pass the returned `keyFacts.images` URLs to your host's vision tool
- **A list of N companies / URLs / HS queries** → ALWAYS use `--inputs` bulk shape, never loop
- **Who actually ships / imports / exports a product** → `product shipments --hs_code <code>` or `shipments search --hs_code_6 <6digit> --dest <iso2>` — real per-shipment customs records, not a model guess
- **A company's real shipment history** → `company shipments --name "<company>"` (matches exporter or importer)
- **Trade flowing on a route / lane** → `route shipments --origin <iso2> --dest <iso2>`
- **Price / value evidence for a product** → `price shipments --hs_code <code> [--min_weight_kg N]`
- **One shipment's full record or raw provenance** → `shipments get --id <recordId>` / `evidence show --id <recordId>`

## Trade-shipment records

`shipments`, `company/product/route/price shipments`, and `evidence show` query actual per-shipment customs + bill-of-lading records — hard evidence of who shipped what, where, when, and (where reported) at what value. Use them when the user wants real trade activity, not website inference. Reference companies by name (`company shipments`) or products by HS code / 6-digit prefix; filter routes by ISO-2 countries.

Every shipment response carries a `coverage` envelope: `{coveredCountries[], periodStart, periodEnd, status, confidenceLevel, completenessRatio, usageGuidance, blindSpot}`. Read it before drawing conclusions — `status: covered` is strong evidence; `partial`/`limited` is directional only; `unavailable` means there is no usable data for that query or period, so do not assert absence of trade. Surface the `coverage` honestly to the user instead of overstating completeness.

Every command is `eximagent <category> [<subcategory>] <verb> [--flag value ...]`. The `exim` prefix is optional:

```bash
eximagent profile get             # preferred, agent-natural
eximagent exim profile get        # also works (explicit)
```

Args are camelCase across the surface (`exporterCountry`, `hsCode`, `businessType`). Kebab-case (`--hs-code`) is accepted as an alias.

Exit codes: `0` success / `1` recoverable (retry with backoff) / `2` fatal (surface to user) / `64` usage error (fix the call).

## Universal flags

- `--inputs <path|->` — bulk input: NDJSON list, one entity per line. Server processes the batch with bounded concurrency. Output: one streamed NDJSON document covering the whole batch.
- `--dry-run` — preview only; sets `confirmed=false` + `dryRun=true` server-side. Safe wrapper for any billable / irreversible command.
- `--profile <name>` — switch saved account. Same as `EXIMAGENT_PROFILE=<name>`.
- `--strict` — single-input only: opt back into the blocking-candidates flow on ambiguous input. Default is auto-pick.
- `--output yaml|table|json` — output format (default: JSON / NDJSON).
- `--stream` — NDJSON stream events for long-running commands.

```bash
# Preview a search without burning credits:
eximagent --dry-run search run --product "<product>" --location DE --hsCode 090111

# Bulk: one tool call enriches all rows from an NDJSON file
eximagent enrich company --inputs companies.ndjson

# Multi-account:
eximagent --profile client-a whoami
```

## Clarification-first — for single-shot human prompts

The user often speaks in vague human-like prompts ("find me some buyers", "send outreach", "what's the tariff"). Your job:

1. **Ask back before guessing.** If a required arg is missing or ambiguous, ask ONE concrete question, then wait. Never invent product names, target countries, HS codes, or recipient titles.
2. **Run `eximagent profile get` first** to ground in user defaults (product, targets, signature, incoterm, timezone).
3. **Preview billable / irreversible calls** with `--dry-run` and present the plan + expected cost before confirming.
4. **Never blind-retry `INVALID_ARG`.** That is your bug; read `error.details.expected` and `did_you_mean`, fix the call shape.

### Worked example — vague prompt → clarify → execute

User: *"find me some buyers"*

```bash
eximagent profile get
```

If profile is empty (`{exists:false, profile:null}`):

You (to user): "Quick — what product are you exporting, which country market are you targeting, and the HS code if you know it?"

User: *"<product> to <target country>"*

```bash
eximagent hscode search --query "<product>"
```

Pick the right HS code with the user. Then preview:

```bash
eximagent --dry-run search run --product "<product>" --location <CC> --hsCode <code> --direction buyers
```

After explicit user "go":

```bash
eximagent --stream search run --product "<product>" --location <CC> --hsCode <code> --direction buyers --confirmed
```

## Cold start (every fresh session)

```bash
eximagent whoami      # verify auth
eximagent profile get # ground in user defaults
```

If profile is empty, gather business basics from the user and `profile extract --from text --utterance "..."` before any large workflow.

## Token economy — never loop tool calls over a list

Every per-entity tool accepts `--inputs <file|->` with one entity per NDJSON line. The server fans out with bounded concurrency and streams a single result back. One agent tool call → one streamed batch result → one context fill on your side.

**The rule:** for any list of more than ~5 items, use `--inputs`. Never loop a per-entity command over a list. Looping burns the host's token budget on N copies of the same prompt + N copies of the same tool-result framing.

```bash
# WRONG — burns token budget per row:
#   for url in $(cat list.txt); do eximagent enrich company --url "$url"; done

# RIGHT — one bulk call, one streamed result:
jq -R '{url: .}' list.txt | eximagent enrich company --inputs -
```

Bulk output is NDJSON: one `{kind:"row", index, input, output, status, autoResolved?, alternatives?, costCents, durationMs}` event per row, terminal `{kind:"complete", rows, ok, autoResolved, failed, totalCostCents, totalDurationMs}`. The terminal event is the summary the user wants — surface it.

## Scale and batching rules

| collection size | safe pattern | notes |
|---|---|---|
| 1-10 | single calls or bulk; either is fine | tiny enough that loops do not hurt much, but bulk is still preferred for consistency |
| 10-50 | bulk only; shortlist before contact enrichment | company-level enrichment ok across the whole set |
| 50-200 | bulk; ALWAYS shortlist before contact enrichment | contact enrichment on a noisy 100+ list wastes budget |
| 200+ | bulk only; shortlist aggressively; consider `--limit` / `--priority` on enrich-contacts | review the shortlist before billable enrichment |

`collection items list` supports cursor pagination — fetch the whole collection without size limits. `enrich contacts` supports `--priority high|medium|low`, `--limit N`, `--row-ids <csv>`, `--only-with-website`, `--max-cost-cents N` to scope the run inside a large collection without creating a shortlist collection.

**Bulk hard caps**: `--inputs` rows max **1000 per call**. Above that → `INVALID_ARG`. Split into multiple sequential bulk calls if the user has >1000 rows. Server-side concurrency caps at **25 workers**; passing `--concurrency 100` gets silently clamped (response `started` event surfaces `concurrencyClamped:true`).

**Long-running ops** (`enrich contacts` on 200+ rows, `search run --confirmed=true` pipeline): wall time **5-10 min** is normal. The CLI prints a stderr heartbeat `[eximagent] still working (Ns)` every 30s so you know the call is alive, and retries transient failures with backoff. The per-call timeout defaults to 180s — raise it for long pipelines with `EXIMAGENT_TIMEOUT_MS`. DO NOT kill the call before the response lands.

**`run` disambiguation**: `eximagent exim run status <runId>` and `eximagent exim run summary <runId>` are top-level commands for any search/pipeline run. `eximagent exim search run` is the search-pipeline kickoff. Different verbs, same word — read the path carefully.

**Preview→confirm binding**: `exim search run --confirmed=false` returns `previewToken: "pv_..."`. Pass that same token back as `--previewToken` on the `--confirmed=true` call. If any other arg drifts between the two calls (typo fix, HS code added, location reword), server rejects with `INVALID_ARG: previewToken mismatch` — re-run preview to get a fresh token.

## Auto-pick on ambiguity (bulk default)

When an input is ambiguous (company name matches multiple plausible websites, HS prefix matches multiple chapters, country alias has alternates), the server picks the top-1 candidate by confidence and emits the row with `autoResolved: true` and `alternatives: [{candidate, score, snippet}, ...]` so you and the user can audit later.

- Bulk runs NEVER block on candidates. They never emit a `status:"candidates"` event.
- Single runs auto-pick by default. Pass `--strict` to opt back into the blocking-candidates flow when the user explicitly wants interactive disambiguation.
- If a pick is wrong, the user asks the agent to redo just that row — overall productivity is far better than confirming every row up front.

## Forward momentum — never stop after a single step

Every tool response carries `nextActions: NextAction[]` with the most-natural next steps for the result state. Each entry: `{verb, command, args, why, autoRunWhenInScope}`. Your job:

1. After ANY tool returns successfully, read `nextActions[]` BEFORE replying to the user.
2. If exactly one action has `autoRunWhenInScope: true` AND the user's original ask covers this step, auto-execute WITHOUT confirming.
3. Otherwise surface `nextActions[]` to the user as a numbered pick list — one line each.
4. NEVER say "I'm done" if `nextActions` is non-empty — explicitly state the open steps + ask the user to pick or say "skip remaining".

Example: search.run returns 290 companies. The response carries `nextActions: [{verb:"enrich-company", autoRunWhenInScope:true}, {verb:"enrich-contacts"}, {verb:"draft-outreach"}]`. User said "find <market> buyers for <product> ready for outreach" — that scope covers enrichment + drafts. Auto-continue.

## Reporting discipline — driver must always know what you are doing

For ANY multi-step workflow (recipe + scope = chain of tool calls), you MUST emit five narration moments. These are non-negotiable — silent multi-step runs violate the platform contract.

1. **Plan card** before the first billable step. Format: numbered step list with ETA + cost forecast per step, total ETA, total cost forecast, captured scope.
2. **Per-step ENTRY** before each tool call: `[2/4] enriching 30 companies on shortlist...`
3. **Per-step EXIT** after each tool returns: `[2/4] done · 30 enriched · 28 with valid website · 2 unreachable · 78s · $0.61 cumulative`
4. **Decision rationale** when picking a non-obvious branch: `"Picking enrich-company before enrich-contacts because contacts need company data"`
5. **Final English summary** as a colleague would report — not raw JSON. 2-3 sentences. What was done, key numbers, what user should know.

Skip-transparency: when you intentionally skip a step (`[3/4 SKIPPED] enrich contacts (scope: shortlist-only)`) — say so. Retry-narration: when a step partially fails and retries — say so. The driver should never have to guess.

## Workflow state on collections

`collection get` / `collection list` / `collection items list` / `run summary` all carry `enrichmentStatus: {company, contacts, drafts}` (`not-started` / `partial` / `complete`). Use this to know whether enrich/draft has already happened on a collection — never re-derive, never re-ask the user.

## Workflow runs (multi-step recipes) — NON-BLOCKING, mandatory poll pattern

Recipe macro commands (`eximagent workflow buyer-discovery`, `workflow supplier-vetting`, `workflow run --recipe <name>`) AND `eximagent ask "<prompt>"` are NON-BLOCKING. The call returns in <2s with `{workflowRunId, status:"pending"}` — this is NORMAL, NOT a failure, NOT the result. The recipe runs in the background (no Bash-tool timeout). You MUST follow this exact pattern:

1. Kick: `eximagent workflow buyer-discovery ... --output json` → capture `.workflowRunId`. `status:"pending"` is success.
2. Poll: `eximagent workflow watch --workflowRunId <id>` in a loop until the response has `done:true` (terminal: completed/failed/cancelled/rolled-back). Sleep ~10-20s between polls. NEVER conclude from the kickoff response alone.
3. Read result: once `done:true`, `eximagent workflow status --workflowRunId <id>` returns `.envelope` = `{englishSummary, qualityScore, findings, provenance, nextActions, likelyFollowups}`. Speak the `englishSummary`; act on `nextActions`.

NEVER treat `status:"pending"` as an error or an empty result. NEVER read `outputs`/`envelope` off the kickoff response (it is not there yet — only after `watch` reports `done:true`). If you skip the `watch` loop you will report "0 results" on a run that is still working.

Other workflow verbs (read-only / control, work mid-run): `workflow plan --recipe <name> --scope <s>` (pre-run steps+ETA+cost card, zero spend — call this BEFORE kicking to show + confirm the plan), `workflow list`, `workflow replay --workflowRunId X` (English narrative of a past run), `workflow audit --workflowRunId X` (compliance log + reproducibility hash), `workflow pause|resume|cancel|modify --workflowRunId X`, `workflow rollback --workflowRunId X` (undo run-materialized collections).

workflowContext envelope on responses inside a recipe: `{recipe, step, totalSteps, scope, spentCents, percentComplete, etaToCompleteMs, nextStep}`. workflowBanner field on responses: structured stderr line the CLI renders automatically.

## Output protocol

- **stdout** = NDJSON. One JSON object per line for streams, single object for one-shot.
- **stderr** = short human breadcrumbs + structured `{error:{code,message,details?}}` on failure. Never parse stderr as data.
- Long-running commands emit stage-level events (`{kind:"stage", stage, completed, total, etaMs}`) plus heartbeats so a quiet in-flight run is visibly healthy, then a terminal `{kind:"complete"|"failed"}`.
- Use `eximagent run status <runId>` for an on-demand snapshot of any run and `eximagent run summary <runId>` for the per-stage durations + counts + drops + total cost when it finishes.

## Confidence model — read every field's tag, speak accordingly

Every contact and company field returned by an enrichment tool carries `{value, source, confidence}`. Levels:

- **verified** — upstream provider confirmed the contact. Present as fact: *"Hans Schmidt, procurement manager at <company>."*
- **extracted** — pulled from crawled markdown via regex or model inference. Candidate data: *"An email matching `name@<company>.example` was found on their site."* Never call it verified.
- **heuristic** — derived from secondary signals (LinkedIn company-page activity, page-context inference). Suggestion: *"Likely a procurement role based on LinkedIn signals."*
- **inferred** — model guess from context. Hypothesis: *"Probably an importer of <product> based on their about-page description."* Always frame as opinion, not fact.

When summarizing a row to the user, lead with verified facts and qualify everything below verified.

## Multimodal — image URLs in the output

Tool output carries images:

- `keyFacts.images: [{url, alt, hint}]` — company-level images (products, facility, team, other) extracted from the crawled site, filtered for legitimate product/facility imagery.
- `sellingProducts[i].imageUrls` / `buyingProducts[i].imageUrls` — per-product photos when the page associates them.

If your host model is multimodal, **route the image URLs directly to its vision tool** before describing in text. This is the highest-bandwidth signal for "does this company actually do what their text claims".

```
# Example agent flow:
# 1. eximagent enrich company --inputs shortlist.ndjson  → rows with keyFacts.images[]
# 2. for each row, host's vision tool reads the image URLs
# 3. agent answers "yes this looks like a specialty <product> manufacturer" with image-grounded evidence
```

Anti-pattern: do NOT paste image URLs to the user as text-only links and ask them to open. Feed images to vision. Phase 1 stores image URLs only (no byte storage); plan around source-page rot by re-enriching when needed.

## State and references — bare name or id

Pass any collection, corridor, template, company, knowledge, product, or bookmark by its bare name or its id directly to the matching arg. The server resolves a UUID-shaped value as an Id, otherwise as a name. No sigil, no prefix.

```bash
eximagent collection get --collectionId "my-buyers-q2"
eximagent corridor remove --name "my-lane"
eximagent email draft --collectionId "my-buyers-q2" --templateName "cold-intro"
```

If a reference does not resolve, you get `NOT_FOUND`. Call the matching list command to discover real names, then retry. The current user profile is a singleton — use `profile get`.

## Discovering existing state

There is no global "list everything" command. Use the per-kind list:

```bash
eximagent collection list
eximagent corridor list
eximagent template list
eximagent kb list
eximagent products list
eximagent reminder list
eximagent monitor list
```

## Error recovery (typed — never blind-retry)

| code | retry? | action |
|------|--------|--------|
| `INVALID_ARG` | NO | Your bug. Read `error.details.expected` + `did_you_mean` (and `error.details.enum` for enum args — those are the allowed values). Args are camelCase. |
| `NOT_FOUND` | NO | Reference does not resolve. Call the matching list command first, then retry. |
| `UPSTREAM_ERROR` | YES | Upstream provider 5xx/4xx. Exponential backoff, retry once. If still failing, surface `traceId`. Fall back to authoritative web search when the upstream is the only source. |
| `RATE_LIMITED` | YES | Wait per `error.details.retryAfterMs`, retry up to 3x. |
| `BUSY` | YES | Another action in-flight on same context. Wait + retry. |
| `FORBIDDEN` | NO | Auth failed. Surface "run `eximagent login`" to the user. |
| `INTERNAL_ERROR` | NO | Unexpected server fault. Surface `traceId`. |

## When to stop and ask the user

Pause and ask explicitly when:

- The user's product, target market, or HS code is ambiguous and `profile get` does not resolve it.
- A billable run >$1 estimated cost is about to fire and the user has not confirmed.
- Multiple plausible company identities resolve and the user wants the right one before paying for enrichment (use `--strict` on single calls; in bulk, auto-pick proceeds and the user can audit `autoResolved` rows after).
- `email send` is about to flush — never send without explicit user "yes" / "send" / "confirm".
- The user's framing of "buyers" vs "sellers" is unclear in context.

In every other case, decide and proceed.

## Anti-patterns

- Looping a per-entity command over a list. Use `--inputs` bulk shape.
- Enriching contacts on a 300+ raw collection before shortlisting. Always shortlist by priority or score first.
- Treating `extracted` emails / phones as verified. Read the confidence tag.
- Presenting image URLs to the user as text links. Feed images to your host's vision tool.
- Sending email without explicit user confirmation.
- Blind-retrying `INVALID_ARG`.
- Asking the user "which one?" on every ambiguous bulk row. Auto-pick + audit after.
- Inventing commands not in the surface below. If a command is not listed, it does not exist.
- Using snake_case args (`hs_code`). Use camelCase (`hsCode`); kebab-case alias also accepted.
- Invoking `_admin/*` commands. Operator-tier, hidden.

## Recovery playbook

- **Collection appears empty mid-run** → run is likely still healthy. Tail stage events or `eximagent run status <runId>` to confirm progress. Do not declare failure.
- **`BUSY` on the user context** → wait `retryAfterMs`, retry. Surface a one-line "in-flight elsewhere, retrying in Ns" breadcrumb.
- **Preview text contradicts intent** → `--direction buyers|sellers` makes the intent explicit. Re-issue the preview before paying.
- **Bulk row count smaller than input** → check `failed` count + per-row `status`. Re-run only the failed indices.
- **`enrich contacts` returns 0 verified** → fall back to company-level signals + manual review. Do not double down on the same call.
- **Source page rot on an image** → re-enrich the company (crawl cache is 100d; image URLs refresh).
- **A wrong `autoResolved` pick** → re-run that one row with `--strict` (single) and let the user pick from `alternatives`.

## Minimum viable output per workflow

A "good" agent response includes these for each common workflow:

- **Buyer discovery** → collection name, total companies, top 5–10 by score, data-quality caveat (drawing on confidence tags), recommended next step (shortlist + enrich).
- **Tariff lookup** → corridor (exporter → importer), HS code, duty rate(s), source attribution, notes on remedies if any.
- **Company enrichment (bulk)** → row count, ok / autoResolved / failed counts, total cost, 2–3 worth-mentioning highlights (specialty signals, scale signals, image-grounded confirmations), recommended next step.
- **Outreach draft** → recipient count, subject + preview of one draft, "ready to send?" confirmation prompt.
- **Sanctions check** → hit / no-hit, program (OFAC SDN / SDGT / etc.), alias matched, advisory caveat.

## Tooling expectations

eximagent is not a complete workflow tool. Reach outside the CLI for:

- **Spreadsheets** for human review of shortlists and exports. CSV-to-NDJSON: `jq -R '{url: .}' < list.txt | eximagent ... --inputs -`.
- **Web validation** when an `autoResolved` pick looks suspicious — open the alternative URLs in your host's browser tool.
- **Manual review** for contact-extraction noise before any outreach run on extracted contacts.
- **Vision tool** for image URLs from any enrich / crawl output (multimodal hosts only).

## Reliability notes

- Buyer discovery surfaces some non-buyer pages — confidence tags + shortlist step are the filter.
- Extracted emails / phones may be noisy — describe them as candidate data until verified.
- Contact enrichment may return 0 verified contacts even on legitimate companies. Fall back to company-level signals.
- Long batch operations may have failed rows even when the batch completes — read `failed` count + per-row `status` and re-run the failures with the same `--inputs`.

## Recommended workflow patterns

### Buyer discovery

```bash
eximagent profile get
eximagent hscode search --query "<product>"
eximagent --dry-run search run --product "<product>" --location <CC> --hsCode <code> --direction buyers
eximagent --stream search run --product "<product>" --location <CC> --hsCode <code> --direction buyers --confirmed
```

### Shortlist before enrichment

```bash
eximagent collection items list --collectionId "<name-or-id>" --priority high --limit 25
```

Or scope enrich-contacts directly inside the large collection:

```bash
eximagent --stream enrich contacts --collectionId "<name-or-id>" --priority high --limit 25 --titles "Procurement,Buyer"
```

### Bulk company enrichment from a CSV/spreadsheet

```bash
jq -R '{url: .}' company-urls.txt | eximagent enrich company --inputs -
# one bulk call, one streamed result, image URLs included per row
```

### Raw markdown of a list of websites

```bash
jq -R '{url: .}' urls.txt | eximagent crawl --inputs -
```

### Outreach last

```bash
eximagent email draft --collectionId "<name-or-id>" --brief "<angle>" --dry-run
eximagent email send --collectionId "<name-or-id>" --confirm
```

## Common chains

### Recipe / `ask` — non-blocking, the canonical multi-step path

```bash
# 1. (optional) show + confirm the plan, zero spend
eximagent workflow plan --recipe buyer-discovery --scope full-outreach --output json
# 2. kick — returns {workflowRunId, status:"pending"} in <2s (NOT the result)
RID=$(eximagent workflow buyer-discovery --product "<product>" --market <CC> --scope full-outreach --output json | jq -r .workflowRunId)
# 3. poll until done (sleep between polls); pending is normal
until eximagent workflow watch --workflowRunId "$RID" --output json | jq -e .done >/dev/null; do sleep 15; done
# 4. read the envelope; speak englishSummary, act on nextActions
eximagent workflow status --workflowRunId "$RID" --output json | jq .envelope
# same shape for: eximagent ask "find <market> buyers for <product> ready for outreach"
```

### Full outreach campaign (manual, vague → confirmed)

```bash
eximagent profile get
eximagent --stream search run --product "<product>" --location <CC> --hsCode <code> --direction buyers --confirmed | tee /tmp/run.ndjson
eximagent collection items list --collectionId "<name-or-id>" --priority high --limit 25
eximagent --stream enrich contacts --collectionId "<name-or-id>" --priority high --titles "Procurement,Buying,Purchasing,Sourcing"
eximagent email draft --collectionId "<name-or-id>" --brief "<your outreach angle>" --dry-run
eximagent email send --collectionId "<name-or-id>" --confirm
```

### Trade Q&A inline

```bash
eximagent tariff --exporter VN --importer DE --product "<product>"
eximagent hscode search --query "<product description>"
eximagent company --name "<company name>"
```

### Refine search

```bash
eximagent search refine --collectionId "<name-or-id>" --addLocation "Austria,Switzerland"
eximagent --stream search run --product "<product>" --location "DE,AT,CH" --hsCode <code> --direction buyers --confirmed
```

## Realistic worked examples

- **Buyers in a target market for a product** → `profile get` → `hscode search --query "<product>"` → `--dry-run search run --product "<product>" --location <country> --hsCode <code> --direction buyers` → confirm → `--stream search run ... --confirmed` → shortlist `--priority high --limit 25` → `enrich company --inputs shortlist.ndjson` → review → `email draft --dry-run`.
- **Importers for a different product** → same pattern, `--product "<product>"`, `--hsCode <code>`, `--direction buyers --location <country>`.
- **Tariff exposure for a product moving between two countries** → `hscode search --query "<product>"` → `tariff --exporter <code> --importer <code> --product "<product>"` → optionally `trade lookup --exporter <code> --importer <code> --hsCode <code> --type all`.
- **Multimodal triage of a bulk-enriched list** → `enrich company --inputs companies.ndjson` → for each `row.output.keyFacts.images`, route URLs to host vision tool → answer "which look like specialty roasters" image-grounded.

## Known limitations

- `linkedin lookup` works best with canonical LinkedIn company URLs. Auto-resolve from company name or website URL is supported but not always correct — provide a canonical URL when you have one.
- `enrich contacts` is collection-scoped; subset is via the row-subset flags inside that collection, not arbitrary cross-collection selection.
- Image extraction is URL-only in Phase 1; if the source page rots, re-enrich.
- `--strict` is single-input only.
- The crawl cache is 100d; for fresher data, the cache key is the canonical URL.
- Long bulk runs respect upstream rate limits; concurrency defaults to 10 and reduces under upstream pressure.

## What not to do

- Do not guess product, market, HS code, titles, or send timing.
- Do not blind-retry `INVALID_ARG`.
- Do not parse stderr as data.
- Do not send email without explicit user confirmation.
- Do not assume collection rows equal verified contacts — read confidence tags.
- Do not assume extracted emails or phones are clean.
- Do not run huge enrichments before creating a shortlist.
- Do not loop tool calls over a list; use `--inputs`.
- Do not invent commands not listed below.
- Do not use snake_case args; use camelCase such as `hsCode`.
- Do not call hidden `_admin/*` commands.

## Command surface (auto-generated from REGISTRY)

### `eximagent collection analyze`
Rank every company in a collection against a plain-language analytical question. Each rank carries a 0-100 score plus one-sentence reasoning grounded in auditable evidence (description, match reasons, trade-potential score). Composes evidence per company from the persisted collection rows, calls an LLM with structured output, returns ranked items.
  - `--collectionId (required) — UUID of the collection to rank`
  - `--question (required) — Plain-language analytical question to rank against`
  - `--rankingCriteria — Optional explicit weighting hints`
  - example: `collection analyze --collectionId js7abc --question "Which of these companies look largest?"`
  - example: `collection analyze --collectionId js7abc --question "Rank by export readiness" --rankingCriteria "weight certificates 2x"`

### `eximagent collection clone`
Deep-clone a collection (copies all items + custom columns; status reset to draft).
  - `--name (required) — Name for cloned collection`
  - `--sourceId (required) — Source collection ID`
  - example: `collection clone --sourceId <id> --name 'germany-q3-clone'`

### `eximagent collection columns add`
Add an AI column to a collection. Per-row cells are populated by a follow-up streaming pass.
  - `--collectionId (required) — Target collection ID`
  - `--label (required) — Column label (display)`
  - `--outputType enum: text|classification|score — Output cell type (defaults to text)`
  - `--prompt (required) — Per-row prompt; agent fills cells using company context`
  - example: `collection add-column --collectionId <id> --label 'Reply likelihood' --prompt 'Score 0-100 based on …' --outputType score`

### `eximagent collection columns run`
Populate per-row AI cells for an existing column. Sequential call per company; idempotent.
  - `--collectionId (required) — Target collection ID`
  - `--columnId (required) — AI column ID (returned by columns add)`
  - example: `collection columns run --collection-id <cid> --column-id reply-likelihood-l8r2`

### `eximagent collection create`
Create an empty collection (manual outreach list, no pipeline run). Use this when the user wants to start a list to add companies/contacts to manually, OR bind a default email template, WITHOUT running the search pipeline. For autonomous buyer discovery use `search run` instead.
  - `--businessType enum: export|import — Trade direction: 'export' (user sells) or 'import' (user buys). Default 'export'.`
  - `--description — Free-text purpose`
  - `--name (required) — Collection name or id`
  - `--templateName — Default outreach template name (must exist — discover via `template list`)`
  - example: `collection create --name q2-buyers`
  - example: `collection create --name q2-outreach --description "Q2 outreach push" --businessType export`
  - example: `collection create --name q2-list --templateName <template-name>`

### `eximagent collection get`
Get a collection by ID — METADATA ONLY (name, totals, priority counts, status). Does NOT return the company rows; for company rows use `exim collection items list`.
  - `--collectionId (required) — Collection ID`
  - example: `collection get --collectionId <id>`

### `eximagent collection items add`
Add a company to a collection. Accepts either an existing companyId OR a companyUrl (auto-upserts into companies on the fly). Use companyName to give the upserted company a display name. Resolve collection by id or name.
  - `--collectionId — Collection ID (e.g. mn7abc...)`
  - `--collectionName — Collection name. Resolved to ID server-side.`
  - `--companyId — company UUID. EITHER this OR companyUrl required.`
  - `--companyName — Company display name (used when upserting from companyUrl). Optional but recommended.`
  - `--companyUrl — Company canonical URL (e.g. https://<company-domain>). Triggers upsert into companies. EITHER this OR companyId required.`
  - `--priority enum: high|medium|low — Priority bucket`
  - example: `collection items add --collectionId <cid> --companyUrl https://<company-domain> --companyName "<Company Name>"`
  - example: `collection items add --collectionName germany-q2-prospects --companyId <id> --priority high`

### `eximagent collection items filter`
Filter rows in a collection: drop rows below minScore / not matching priority / without enriched mails. Idempotent — applies to current rows only.
  - `--minScore — Minimum score (drop rows below)`
  - `--name (required) — Collection name`
  - `--priority enum: high|medium|low — Keep ONLY rows of this priority (drop others)`
  - `--requireMail — Drop rows whose company has no enriched mails (default false)`
  - example: `collection items filter --name germany-q2 --priority high`
  - example: `collection items filter --name germany-q2 --min-score 0.6 --require-mail true`

### `eximagent collection items list`
List the company rows inside a collection — name, score, priority, country, enriched mails, outreach stage. Use this to actually see WHO is in the collection after a search/recipe has materialized. `collection get` returns only metadata (totals + priority counts); this returns the rows. Sorted by score descending. Target the collection with --collectionId (the id from recipe/search output, or the collection name) — same arg as every other collection/email/enrich command — or --name; defaults to your most-recent collection if neither given. Cursor-paginated (nextCursor), no size cap.
  - `--collectionId — Collection to list — accepts the collectionId returned by search/recipe outputs OR the collection name. Same arg name as every other collection/email/enrich command. Use this when chaining from a recipe.`
  - `--cursor — Pagination offset (0-indexed). Feed the previous response.nextCursor to get the next page. Default 0.`
  - `--limit — Max rows to return per page (default 50, max 500). For full enumeration use nextCursor pagination.`
  - `--name — Collection name (alternative to --collectionId)`
  - `--priority enum: high|low|medium — Filter to this priority only`
  - example: `collection items list`
  - example: `collection items list --collectionId <id-from-recipe-output> --priority high`
  - example: `collection items list --name germany-q2 --limit 20`
  - example: `collection items list --collectionId germany-q2 --priority high`

### `eximagent collection items merge`
Merge rows from one collection into another. De-duplicates by companyId. Optionally soft-deletes the source.
  - `--from (required) — Source collection name (rows copied FROM here)`
  - `--into (required) — Destination collection name (rows merged INTO here)`
  - `--removeSource — Soft-delete source after merge (default false)`
  - example: `collection items merge --from <collection-a> --into <collection-b>`

### `eximagent collection items remove`
Remove a single row from a collection. Either pass --itemId directly OR pass --collectionId/--collectionName + --companyUrl to look up and remove the row for that company. Use `collection items list` to discover names + URLs.
  - `--collectionId — Collection ID. Required when removing by companyUrl.`
  - `--collectionName — Collection name (resolved to ID server-side). Alternative to collectionId.`
  - `--companyUrl — Company canonical URL — resolves to the row in the named collection and removes it.`
  - `--itemId — Collection-item ID (direct row delete). Alternative to companyUrl.`
  - example: `collection items remove --itemId <id>`
  - example: `collection items remove --collectionName germany-q2-prospects --companyUrl https://<company-domain>`

### `eximagent collection list`
List user collections (most recent first). Tombstoned (soft-deleted) collections are excluded by default — pass --includeDeleted true to see them.
  - `--includeDeleted — Include tombstoned (soft-deleted) collections (default false)`
  - example: `collection list`
  - example: `collection list --includeDeleted true`

### `eximagent collection remove`
Soft-delete a collection (tombstoned; mention chips render as deleted).
  - `--collectionId (required) — Collection ID to soft-delete`
  - example: `collection remove --collectionId <id>`

### `eximagent collection rename`
Rename a collection.
  - `--collectionId (required) — Collection ID to rename`
  - `--name (required) — New name`
  - example: `collection rename --collectionId <id> --name 'germany-q3'`

### `eximagent collection similar`
Find user's existing collections similar to a free-text query. Token-overlap scoring on (name, industry, targetCountry, query).
  - `--limit — Max suggestions (default 5)`
  - `--query (required) — Free-text criteria description (industry, region, product, etc.)`
  - example: `collection similar --query "<industry> importers in <market>"`

### `eximagent collection stats`
Aggregate stats for a collection: totals, priority counts, conversion rate, runtime.
  - `--name (required) — Collection name or id`
  - example: `collection stats --name germany-q2`

### `eximagent collection update`
Update collection metadata (description, businessType, industry, targetCountry). For rename use `collection rename`. For items use `collection items add/remove`.
  - `--businessType enum: export|import — Trade direction`
  - `--collectionId (required) — Collection ID to update`
  - `--description — New free-text purpose / notes`
  - `--industry — New industry hint`
  - `--targetCountry — New target country`
  - example: `collection update --collectionId <id> --description "Q2 push, German roasters"`
  - example: `collection update --collectionId <id> --industry <industry> --targetCountry <country>`

### `eximagent company`
Company URL + profile + summary.
  - `--address — Location signal (city, state, country)`
  - `--country — Country (any format)`
  - `--name (required) — Company name`
  - `--product — Product or industry hint`
  - example: `company --name "<company-name>"`
  - example: `company --company "<company-name>" --address <city>`

### `eximagent company-memory get`
Pull the latest outbound + inbound email exchange between the user and a company (PG email + sg_* events). Use with a company name or id to surface negotiation context (last quote subject, last reply snippet).
  - `--companyId (required) — company UUID of the counterparty`
  - example: `company-memory get --companyId <company UUID>`

### `eximagent company shipments`
Shipments where a company is exporter or importer
  - `--limit — max rows (1-1000, default 100)`
  - `--name (required) — company name`

### `eximagent contacts add`
Manually add a contact (employee) tied to a company. Accepts either an existing companyId OR a companyUrl (auto-upserts into companies on the fly). Source tagged as 'manual'.
  - `--companyId — company UUID. EITHER this OR companyUrl required.`
  - `--companyName — Display name (used when upserting from companyUrl).`
  - `--companyUrl — Company canonical URL (e.g. https://<company-domain>). Triggers upsert into companies. EITHER this OR companyId required.`
  - `--department — Department`
  - `--linkedinUrl — LinkedIn URL`
  - `--location — Location`
  - `--mail (required) — Email address`
  - `--name — Full name`
  - `--phone — Phone`
  - `--title — Job title`
  - example: `contacts add --companyUrl https://<company-domain> --companyName '<Company Name>' --mail name@<company-domain> --name '<Full Name>' --title 'Procurement Manager'`
  - example: `contacts add --companyId <id> --mail name@<company-domain> --title 'CPO'`

### `eximagent corridor get`
Get a saved trade corridor by name. Returns the full corridor record (exporter, importer, HS code, defaults, tariff snapshot if any). Run `corridor list` first to find the name.
  - `--name (required) — Corridor name or id (e.g. fr-de-widgets). Discover via `corridor list`.`
  - example: `corridor get --name fr-de-widgets`

### `eximagent corridor list`
List saved trade corridors (most recent first).
  (no args)
  - example: `corridor list`

### `eximagent corridor remove`
Remove a saved corridor by name.
  - `--name (required) — Corridor name to remove`
  - example: `corridor remove --name <corridor-name>`

### `eximagent corridor save`
Save a trade lane (exporter × importer × HS code) as a reusable corridor, referenced later by its name or id and (when starred) surfaced as an empty-chat starter chip.
  - `--defaultCurrency — ISO 4217 currency (e.g. 'USD')`
  - `--defaultIncoterm — Default Incoterm (e.g. 'FOB', 'CIF')`
  - `--exporterCountry (required) — Exporter country — accepts country name, ISO2, ISO3`
  - `--hsCode — HS code for this corridor (optional — anchor later via update)`
  - `--importerCountry (required) — Importer country — accepts country name, ISO2, ISO3`
  - `--name (required) — Corridor name or id (kebab-case, e.g. fr-de-widgets)`
  - `--star — Star this corridor (surfaces as starter chip)`
  - example: `corridor save --name fr-de-widgets --exporter-country France --importer-country Germany --hs-code 854430 --default-incoterm FOB --default-currency EUR --star true`

### `eximagent country resolve`
Resolve a country by name/ISO2/ISO3
  - `--query (required) — Country identifier: name, ISO2, or ISO3`
  - example: `country resolve USA`
  - example: `country resolve --query "Viet Nam"`
  - example: `country resolve VN`

### `eximagent crawl run`
Fetch raw markdown content for a website. Returns the homepage plus a small set of structurally-relevant internal pages. Reuses the same crawl cache the search pipeline uses, so repeat asks within 100 days are free. For batch crawls use the bulk shape: `eximagent --inputs urls.ndjson exim crawl run` where each row is `{"url": "https://..."}` — one tool call, N parallel crawls, one streamed result.
  - `--url (required) — Website URL to crawl (https://...). Returns the homepage plus a small set of internal pages (about/contact/products if discovered). Cache-aware: 100-day TTL keyed by canonical URL.`
  - example: `crawl run --url https://example.com`
  - example: `eximagent --inputs sites.ndjson exim crawl run    # bulk shape`

### `eximagent email cancel`
Cancel an in-flight email send for a collection. Pipeline checks the cancel flag between drafts; partial sends already flushed cannot be unsent.
  - `--collectionId (required) — Collection whose in-flight send should be cancelled`
  - example: `email cancel --collection-id <cid>`

### `eximagent email draft`
Draft personalized outbound emails per recipient WITHOUT sending (preview-only). Renders ConfirmSendCard with countdown; agent calls `email run` (without dryRun) on user confirm.
  - `--brief (required) — Outreach brief — what to mention, tone, ask`
  - `--ccMe — CC the sender on each outbound`
  - `--collectionId (required) — UUID of the collection to draft for`
  - `--senderEmail (required) — From email (rendered into drafts)`
  - `--senderName (required) — Sender display name`
  - `--trackingEnabled — Enable open/click tracking on the eventual send`
  - example: `email draft --collectionId <id> --brief 'angle for outreach; soft ask' --senderEmail you@x.com --senderName 'Your Name'`

### `eximagent email followup`
Schedule a non-responder follow-up. Wraps monitor.create with signal='non-responder-followup'; on fire, agent assembles drafts + ConfirmSendCard before flushing.
  - `--after (required) — Wait window before firing (cron or 'every Xd' / 'every Xh')`
  - `--brief (required) — Follow-up brief (what to add in the second touch)`
  - `--campaignId (required) — Source campaign / collection id`
  - `--name (required) — Follow-up name or id`
  - example: `email followup --campaign-id js7abc --name dach-q2-followup --after 'every 5d' --brief 'gentle nudge with case-study link'`

### `eximagent email history`
List sent emails with delivery + engagement counts (delivered / opened / clicked / bounced).
  - `--collectionId — Filter by collection ID`
  - `--limit — Max rows (default 50)`
  - example: `email history`
  - example: `email history --collectionId <id>`

### `eximagent email send`
Draft personalized outbound emails per recipient (profile-aware) and send. PREVIEW BY DEFAULT — without --confirm it only drafts and returns a preview, never sends. Pass --confirm to flush; the pre-send sanity guard (signature/DKIM/OFAC/template-stage/unsubscribe/recent-dry-run/timezone) runs first and refuses on any failed check. Writes the sent record on send.
  - `--brief (required) — Outreach brief — what to mention, tone, ask`
  - `--ccMe — CC the sender on each outbound`
  - `--collectionId (required) — UUID of the collection to email`
  - `--confirm — Required to actually flush emails. WITHOUT --confirm this is always a safe preview (no send), even without --dryRun. The pre-send sanity guard runs only when --confirm is set.`
  - `--dryRun — Explicit preview alias. Equivalent to omitting --confirm — generates drafts without sending.`
  - `--includeLowConfidence — Include low-confidence recipient emails (role aliases info@/sales@, no-reply, single-letter local, image-filename addresses). Default false — these are auto-pruned and counted in stats.autoPruned.`
  - `--senderEmail (required) — From email (must be operator-owned domain)`
  - `--senderName (required) — Sender display name`
  - `--trackingEnabled — Enable open/click tracking`
  - example: `email send --collectionId <id> --brief 'angle; soft ask' --senderEmail you@x.com --senderName 'Your Name'  # preview`
  - example: `email send --collectionId <id> --brief 'angle; soft ask' --senderEmail you@x.com --senderName 'Your Name' --confirm  # flush after guard`

### `eximagent employees filter`
Filter persisted contacts of a collection by title / department / verification confidence. Pure in-memory filter over the collection rows that enrich contacts already persisted — never re-discovers, never billable. Recall-first doctrine: discover unfiltered, narrow after.
  - `--collectionId (required) — UUID of the target collection`
  - `--confidence enum: extracted|verified — Keep only rows of this verification confidence`
  - `--departments — Comma-separated departments to keep (case-insensitive)`
  - `--titles — Comma-separated job-title substrings to keep (case-insensitive)`
  - example: `employees filter --collectionId js7abc... --departments procurement`
  - example: `employees filter --collectionId js7abc... --titles "procurement manager,buyer" --confidence verified`

### `eximagent employees rank`
Rank persisted contacts of a collection by one of three criteria: verified-first (deliverability confidence), seniority (title words like director/VP/head), or engagement-likelihood (upstream likely-to-engage flag). Pure in-memory ranking over already-persisted rows; never re-discovers, never billable.
  - `--by (required) enum: engagement-likelihood|seniority|verified-first — Ranking criterion: by deliverability confidence, by seniority words in title, or by likely-to-engage flag.`
  - `--collectionId (required) — UUID of the target collection`
  - `--limit — Max ranked rows to return (top N).`
  - example: `employees rank --collectionId js7abc... --by seniority`
  - example: `employees rank --collectionId js7abc... --by verified-first --limit 10`

### `eximagent enrich company`
Structured company profile from the website itself. Crawls homepage + a few internal pages and runs a fast model on raw content to extract: description, industries, selling products (with HS codes), buying products (with HS codes), certificates, expos attended, international trade markers, nearest port, branches, trade potential 0-100. Also regex-extracts contact mails + phones from the crawled markdown (with noise filtering). Two input modes: (a) --url when you have the canonical site; (b) --company when you only have the name — we search the web and either run on the single plausible site OR return a candidates list. Cache-aware (100d).
  - `--company — Company name — we resolve a likely website via web search. Ambiguous names return a candidates list to disambiguate (ask the user to pick, then re-run with --url).`
  - `--strict — When set, ambiguous --company names return a blocking candidates list instead of auto-picking the top match. Single-input only; bulk runs always auto-pick.`
  - `--url — Company website URL (https://...). Validated for reachability + that the host is a real business site (not a blog / news / social / aggregator).`
  - exactly one of: `--company` / `--url`
  - example: `enrich company --url https://<company-domain>`
  - example: `enrich company --company "<company name>"`

### `eximagent enrich contact`
Re-verify a single employee email via the contact verification provider; fills mail when found.
  - `--employeeId (required) — Employee ID whose email to re-verify`
  - example: `enrich contact --employeeId <id>`

### `eximagent enrich contacts`
Enrich a collection (or a row subset of one) with decision-maker contacts. Upstream people-search + verified-email enrichment. Subset filters: --priority (high/medium/low only), --limit N (top N after filter), --row-ids (explicit collectionItem ids), --only-with-website (skip companies without canonical site), --max-cost-cents (hard budget cap; loop stops at threshold). Confidence model on emitted contacts: verified-source → confidence=verified (deliverable email); probable-source → confidence=extracted (probable email shape). Agents should state verified emails as fact; extracted as candidate data.
  - `--collectionId (required) — UUID of the target collection`
  - `--departments — Comma-separated departments (e.g. procurement,purchasing)`
  - `--limit — Max collection rows to enrich (after --priority / --row-ids filters). Bills only the selected rows.`
  - `--maxCostCents — Hard cost cap in cents. Loop stops once accumulated approximate cost meets this budget.`
  - `--onlyWithWebsite — Skip rows whose company has no canonical website (saves contact-enrich spend on un-crawlable orgs).`
  - `--priority enum: high|low|medium — Filter to rows of this priority only before enriching.`
  - `--rowIds — Comma-separated collectionItem Ids — enrich only this explicit subset.`
  - `--seniorities — Comma-separated seniorities (e.g. director,head,manager)`
  - `--titles — Comma-separated job titles`
  - example: `enrich contacts --collectionId js7abc... --titles 'procurement manager,buyer'`
  - example: `enrich contacts --collectionId js7abc... --priority high --limit 25`
  - example: `enrich contacts --collectionId js7abc... --row-ids id1,id2,id3 --max-cost-cents 500`

### `eximagent evidence show`
Show raw evidence for one shipment (source provenance)
  - `--id (required) — shipment id`

### `eximagent hscode search`
Search HS codes; cascades lexical -> hybrid (embedding) -> llm-expanded as result quality demands.
  - `--level enum: section|chapter|heading|subheading — Restrict to one hierarchy level`
  - `--limit — Max results (1-50, default 5)`
  - `--query (required) — HS code (digits) or product keyword`
  - example: `hscode search --query "frozen shrimp"`
  - example: `hscode search --query <product> --level chapter`
  - example: `hscode search --query 0201  # by code prefix`

### `eximagent kb add`
Register a knowledge-base entry. Accepts either (a) uploaded file via --fileId (from kb upload-url flow), OR (b) text content via --content (no upload needed). Content gets injected into email-gen + profile-extract context.
  - `--businessType enum: export|import — Trade direction`
  - `--content — Extracted text content (for LLM retrieval)`
  - `--fileId — storage ID (from kb upload-url flow). Optional when content is provided text-only.`
  - `--fileSize — Bytes`
  - `--fileType — MIME type`
  - `--filename (required) — Display filename`
  - `--tags — Comma-separated tags`
  - example: `kb add --fileId <id> --filename pricing.pdf --tags 'pricing,sales'`
  - example: `kb add --filename pricing-notes.txt --content 'Our Q2 pricing tier: ...' --tags 'pricing'`

### `eximagent kb get`
Get a knowledge-base entry by filename. Returns the full record including tags + metadata + (truncated) content. For a quick excerpt only, use `kb preview`.
  - `--filename (required) — Filename of the KB entry. Discover via `kb list`.`
  - example: `kb get --filename brochure.pdf`

### `eximagent kb list`
List user knowledge-base entries.
  (no args)
  - example: `kb list`

### `eximagent kb preview`
Preview a knowledge-base file: returns a content excerpt + classification (B/L / COO / invoice / etc.) + tags so user can verify content without downloading.
  - `--kbId (required) — KB entry id`
  - example: `kb preview --kb-id <id>`

### `eximagent kb remove`
Delete a knowledge-base entry by ID.
  - `--id (required) — KB entry ID`
  - example: `kb remove --id <kbId>`

### `eximagent linkedin lookup`
LinkedIn company page batch lookup. Accepts canonical LinkedIn URLs via --urls AND/OR company names or website URLs via --queries — names/URLs auto-resolve to canonical LinkedIn pages via web search before scrape. Cached 100 days per resolved URL. Returns structured profile data keyed by resolved URL plus a resolutions report so the agent can audit which queries mapped to which URL.
  - `--queries — Comma-separated company names OR website URLs OR LinkedIn URLs. Each input is auto-resolved to a canonical linkedin.com/company URL via web search before scraping. Mix freely with --urls; both feed the same batch.`
  - `--urls — Comma-separated LinkedIn company URLs (canonical, e.g. https://linkedin.com/company/<slug>)`
  - example: `linkedin lookup --urls "https://linkedin.com/company/<slug-a>,https://linkedin.com/company/<slug-b>"`
  - example: `linkedin lookup --queries "<Company A>,<Company B>"`
  - example: `linkedin lookup --queries "https://company-a.example.com,https://company-b.example.com"`

### `eximagent monitor cancel`
Cancel (disable) a monitor by name.
  - `--name (required) — Monitor name to cancel`
  - example: `monitor cancel --name germany-replies`

### `eximagent monitor create`
Create a recurring SIGNAL-BASED monitor that fires on trade-domain events (reply arrived, non-responder follow-up, new company matching criteria, tariff change, dead domain). NOT for date-based reminders — use `reminder create` for those.
  - `--description (required) — What this monitor watches`
  - `--name (required) — Monitor name or id`
  - `--schedule (required) — Schedule: hourly|daily|weekly or 'every <N>m|h|d|w' (e.g. 'every 6h')`
  - `--signal (required) enum: reply-arrived|non-responder-followup|new-company-matching-criteria|tariff-change|dead-domain — Signal kind to watch`
  - `--target (required) — Target identifier (collectionId, criteria JSON, etc.)`
  - example: `monitor create --name reply-watch --signal reply-arrived --target campaign-123 --schedule 'every 1h' --description 'Watch for inbound reply'`

### `eximagent monitor get`
Get a saved monitor by name. Returns the full record (signal, target, schedule, enabled). Run `monitor list` first if you need to discover the name.
  - `--name (required) — Monitor name. Discover via `monitor list`.`
  - example: `monitor get --name <monitor-name>`

### `eximagent monitor list`
List user SIGNAL-BASED monitors (enabled by default). Monitors are recurring, fire on trade events (reply-arrived, tariff-change, etc.). NOT the same as `reminder list` (one-shot date-based). When user asks broadly "what watches/alerts do I have", call this AND `reminder list` to surface both.
  - `--includeDisabled — Include disabled monitors (default false)`
  - example: `monitor list`

### `eximagent pipeline stop`
Cancel the user's in-flight pipeline run (if any) and clear busy state. Marks the latest running collection.run as cancelled. Idempotent — no-op when nothing is running. Per unified-collection-non-blocking-pipeline-single-context ADR §3.
  (no args)
  - example: `pipeline stop`

### `eximagent price shipments`
Shipments for an HS code above a weight threshold
  - `--hsCode (required) — HS code (4/6/8/10 digit prefix)`
  - `--limit — max rows (1-1000, default 100)`
  - `--minWeightKg — min gross weight kg`

### `eximagent product shipments`
Shipments matching an HS code prefix
  - `--hsCode (required) — HS code (4/6/8/10 digit prefix)`
  - `--limit — max rows (1-1000, default 100)`

### `eximagent products add`
Save or update a product in the user profile. Image uploaded via products upload-url flow.
  - `--businessType enum: export|import — Trade direction`
  - `--category — Category`
  - `--description — Short product description`
  - `--hsCode — HS code`
  - `--imageId — storage ID for product image (from upload-url flow)`
  - `--moq — Minimum order quantity (e.g. "5 tons")`
  - `--name (required) — Product name or id`
  - example: `products add --name 'premium widget A' --moq '5 tons' --description 'industrial grade, custom spec'`

### `eximagent products get`
Get a saved user product by name. Returns the full product record (description, HS code, MOQ, category, image). Run `products list` first to discover the name.
  - `--name (required) — Product name or id. Discover via `products list`.`
  - example: `products get --name premium-widget-a`

### `eximagent products list`
List user products.
  - `--businessType enum: export|import — Filter`
  - example: `products list`
  - example: `products list --businessType export`

### `eximagent products remove`
Remove a product from the user profile.
  - `--name (required) — Product name to remove`
  - example: `products remove --name premium-widget-a`

### `eximagent profile extract`
Extract user-profile fields (company, products, markets, role, language, contacts) from text utterance OR URL crawl OR file content. Default behavior: persists extracted fields to the profile (apply=true). Pass --apply false to preview the extraction without writing.
  - `--apply — Persist extracted fields to the profile via profile.update (default true). Pass false to preview only.`
  - `--content — Plain-text file content (when from='file')`
  - `--filename — File name for context (when from='file')`
  - `--from (required) enum: text|url|file — Source kind: 'text' = extract from utterance; 'url' = crawl website then extract; 'file' = use provided text content`
  - `--url — Company website URL (when from='url')`
  - `--utterance — Free-text user utterance (when from='text')`
  - example: `profile extract --from text --utterance "I export industrial fans from France"`
  - example: `profile extract --from url --url https://example.com`
  - example: `profile extract --from file --filename brochure.pdf --content "..."`

### `eximagent profile get`
Read the user's business profile.
  (no args)
  - example: `profile get`

### `eximagent profile reset`
Reset the current user profile to empty. Irreversible. Use when the user explicitly asks to "start fresh" / "clear my profile" / "reset". Other artifacts (collections, corridors, templates, etc.) are not touched — remove those with their own `remove` verbs.
  (no args)
  - example: `profile reset`

### `eximagent profile update`
Update fields on the user's business profile. Omitted fields are preserved.
  - `--company — Company name`
  - `--companyDescription — Brief company description`
  - `--companyLinkedin — Company LinkedIn URL`
  - `--description — Free-text user description`
  - `--industries — Comma-separated industries`
  - `--job — Job title`
  - `--note — Internal note`
  - `--preferredLanguage — Preferred language code (e.g. en, vi)`
  - `--role — Role description`
  - `--signature — Email signature`
  - `--sources — Comma-separated sourcing countries`
  - `--targets — Comma-separated target market countries`
  - `--userLinkedin — User LinkedIn URL`
  - `--websites — Comma-separated company / personal websites`
  - example: `profile update --signature 'Best, <name> / <company>'`
  - example: `profile update --industries "<industry>" --targets "<country>,<country>"`

### `eximagent reminder cancel`
Cancel a pending reminder by name.
  - `--name (required) — Reminder name to cancel`
  - example: `reminder cancel --name <reminder-name>`

### `eximagent reminder create`
Create a one-shot DATE-BASED reminder that fires at a specific ISO timestamp (e.g. "remind me Monday 9am to review the German campaign"). NOT for recurring or signal-based watches — for those use `monitor create`. Use `reminder list` to see saved reminders.
  - `--description (required) — What to remember`
  - `--name (required) — Reminder name or id`
  - `--tz — IANA timezone (e.g. Asia/Bangkok) for whenIso when it carries no offset`
  - `--whenIso (required) — When to fire (ISO 8601, e.g. 2026-05-15T10:00:00Z)`
  - example: `reminder create --name <reminder-name> --whenIso 2026-05-15T10:00:00Z --description 'Check reply received'`

### `eximagent reminder get`
Get a saved reminder by name. Returns the full record (description, fireAt, fired). Run `reminder list` first to discover the name.
  - `--name (required) — Reminder name. Discover via `reminder list`.`
  - example: `reminder get --name <reminder-name>`

### `eximagent reminder list`
List user DATE-BASED reminders (pending by default). Reminders are one-shot, fire at a specific ISO timestamp. NOT the same as `monitor list` (recurring trade-signal watches — replies, tariff changes, etc.). When user asks "what reminders do I have", call this AND `monitor list` to surface both.
  - `--includeFired — Include fired reminders (default false)`
  - example: `reminder list`
  - example: `reminder list --includeFired true`

### `eximagent reply show`
Surface the most-recent inbound reply for the user (or scoped to a collection). Returns {fromEmail, subject, snippet, summary, receivedAt}. Populated by the inbound-mail webhook.
  - `--collectionId — Filter to one collection (optional)`
  - example: `reply show`
  - example: `reply show --collection-id <cid>`

### `eximagent route shipments`
Shipments on an origin→destination country route
  - `--dest (required) — Destination country (ISO 2)`
  - `--limit — max rows (1-1000, default 100)`
  - `--origin (required) — Origin country (ISO 2)`

### `eximagent run status`
Current snapshot of a search-pipeline run by runId. Works on in-flight runs (status=running with partial counts) and on finished runs (status=completed/cancelled/failed). Returns the parent collection name + start/end timestamps + companies-found + priority breakdown when available.
  - `--runId (required) — Run UUID returned by `exim search run --confirmed true`.`
  - example: `run status --runId 9b3...`

### `eximagent run summary`
Per-run summary card after a search pipeline finishes (or while it is still running). Returns criteria + query + start/finish timestamps + duration + total companies + priority breakdown + model used + status. For the live snapshot of an in-flight run use `exim run status`; this command is the post-mortem view that prints the full criteria back so the agent can re-run with the same shape.
  - `--runId (required) — Run UUID; finished runs preferred.`
  - example: `run summary --runId 9b3...`

### `eximagent sanctions check`
Screen a name against the OFAC SDN sanctions list (Specially Designated Nationals). Returns matches with program (e.g. SDGT/IRAN/CUBA) and aliases. Substring + alias match. Always advisory — final clearance lives with the founder/legal.
  - `--name (required) — Company or person name to screen against OFAC SDN list`
  - example: `sanctions check --name "<entity-name>"`

### `eximagent search refine`
Preview an additive/subtractive refinement of an existing collection's criteria. Returns a diff card (kind: 'refine-preview') showing which filters add/remove + expected company-count delta. After user confirms, caller should invoke `exim search run --confirmed=true` with merged criteria.
  - `--addExcludeBusinessType — Add to exclude business types (CSV)`
  - `--addExcludeLocation — Add to exclude locations (CSV)`
  - `--addIndustry — Add industry filter`
  - `--addLocation — Add target market(s) — CSV: Austria,Switzerland`
  - `--collectionId (required) — collection UUID to refine`
  - `--removeExcludeBusinessType — Remove exclude business types (CSV)`
  - `--removeExcludeLocation — Remove exclude locations (CSV)`
  - `--removeIndustry — Drop industry filter entirely`
  - `--removeLocation — Remove target market(s) — CSV`
  - example: `search refine --collection-id <cid> --add-location "Austria,Switzerland"`
  - example: `search refine --collection-id <cid> --remove-industry --add-exclude-business-type "retail"`

### `eximagent search run`
Autonomous buyer/importer discovery pipeline. Discovers candidate companies, crawls and scores each, ranks top-K, and materializes a Collection. Pre-pipeline preview is mandatory — first call (confirmed=false) returns a criteria preview; user confirms → second call (confirmed=true) kicks the non-blocking pipeline. Streams progress events to the run.
  - `--category enum: export|import — Trade direction: export (you sell, we find buyers) or import (you buy, we find sellers).`
  - `--confirmed — When true, kicks the pipeline. When false (default), returns a criteria-preview card with a previewToken for user confirmation.`
  - `--direction enum: buyers|sellers — Agent-friendly synonym for --category. "buyers" → export (you sell); "sellers" → import (you buy). Mutually exclusive with --category; if both pass, --direction wins for preview text.`
  - `--excludeBusinessType — Exclude business types`
  - `--excludeLocation — Exclude locations`
  - `--hsCode — HS code (anchors the corridor + tariff context; agent should disambiguate via exim hscode search before kickoff)`
  - `--industry — Industry`
  - `--location — Target market country — accepts country name, ISO2, ISO3 (e.g. "Germany", "DE", "DEU")`
  - `--name — Collection name (auto-slug if omitted)`
  - `--previewToken — Echo back the previewToken from the prior --confirmed=false preview to bind the kickoff to identical args. Server rejects if args drift from what was previewed.`
  - `--product (required) — Product to find buyers/sellers for`
  - `--targetBusinessDescription — Target buyer/seller profile`
  - example: `search run --product "<product>" --location Germany --category import`
  - example: `search run --confirmed true --product "<product>" --location Germany --hs-code 090111`

### `eximagent shipments get`
Get one shipment by deterministic id
  - `--id (required) — shipment id`

### `eximagent shipments search`
Search shipments across all dimensions
  - `--dest — Destination country (ISO 2)`
  - `--hs6 — HS code (first 6 digits)`
  - `--hsCode — HS code (4/6/8/10 digit prefix)`
  - `--limit — max rows (1-1000, default 100)`
  - `--name — company name (case-insensitive substring)`
  - `--origin — Origin country (ISO 2)`

### `eximagent stats show`
Conversational analytics on sent emails: reply-rate / open-rate / click-rate / bounce-rate computed from PG email + sg_* event tables. Optionally scope to a single collection.
  - `--collectionId — Filter by collection id (optional)`
  - `--limit — Max emails to scan (default 1000)`
  - example: `stats show`
  - example: `stats show --collection-id <cid>`
  - example: `stats show --collection-id <cid> --limit 500`

### `eximagent tariff`
Trade/tariff/duty for any corridor. --product optional (omit for corridor overview).
  - `--exporter (required) — Sending country — accepts country name, ISO2, ISO3 (e.g. "Viet Nam", "VN", "VNM")`
  - `--importer (required) — Receiving country — accepts country name, ISO2, ISO3 (e.g. "Germany", "DE", "DEU")`
  - `--product — Product / goods description`
  - example: `tariff --exporter <iso> --importer <iso> --product <product>`
  - example: `tariff --from <country> --to <country> --goods <product>`
  - example: `tariff --exporter <iso> --importer <iso>  # corridor overview, no product`

### `eximagent template add`
Save or update an email template. Variables (e.g. {{name}}, {{company}}) auto-extracted; attachment file-IDs auto-resolved.
  - `--businessType enum: export|import — Trade direction this template targets`
  - `--category — Category (e.g. 'cold-outreach', 'follow-up', 'sales')`
  - `--content (required) — Template body. Supports {{variable}} placeholders + [FILE_ID:storageId:filename] attachment markers.`
  - `--name (required) — Template name or id`
  - `--subject (required) — Subject line (may use {{variables}})`
  - example: `template save --name cold-intro --subject 'Partnership intro' --content 'Hi {{name}}, ...'`

### `eximagent template create`
Save or update an email template. Variables (e.g. {{name}}, {{company}}) auto-extracted; attachment file-IDs auto-resolved.
  - `--businessType enum: export|import — Trade direction this template targets`
  - `--category — Category (e.g. 'cold-outreach', 'follow-up', 'sales')`
  - `--content (required) — Template body. Supports {{variable}} placeholders + [FILE_ID:storageId:filename] attachment markers.`
  - `--name (required) — Template name or id`
  - `--subject (required) — Subject line (may use {{variables}})`
  - example: `template save --name cold-intro --subject 'Partnership intro' --content 'Hi {{name}}, ...'`

### `eximagent template delete`
Delete a saved email template by name. Irreversible. NOT for cancelling a draft batch — for that the draft layer has its own flow.
  - `--name (required) — Template name to delete. Discover via `template list`.`
  - example: `template remove --name <template-name>`

### `eximagent template edit`
Update a saved template. Pass only fields to change; others stay.
  - `--category — New category (replaces if provided)`
  - `--content — New body (replaces if provided; supports {{variables}} + [FILE_ID:...] markers)`
  - `--name (required) — Template name or id`
  - `--subject — New subject (replaces if provided; supports {{variables}})`
  - example: `template edit --name cold-intro --content 'Hi {{name}}, ...'`

### `eximagent template generate`
Generate an email template (subject + body with {{variables}}) from a free-text prompt. Caller saves via `template save`.
  - `--prompt (required) — Prompt: tone + intent + product + audience (e.g. "casual cold outreach for B2B importers")`
  - example: `template generate --prompt 'casual cold-outreach for first contact, B2B importers'`

### `eximagent template get`
Read a saved template by name (full content + variables + attachments).
  - `--name (required) — Template name or id`
  - example: `template recall --name cold-intro`

### `eximagent template list`
List the user saved email templates.
  (no args)
  - example: `template list`

### `eximagent template recall`
Read a saved template by name (full content + variables + attachments).
  - `--name (required) — Template name or id`
  - example: `template recall --name cold-intro`

### `eximagent template remove`
Delete a saved email template by name. Irreversible. NOT for cancelling a draft batch — for that the draft layer has its own flow.
  - `--name (required) — Template name to delete. Discover via `template list`.`
  - example: `template remove --name <template-name>`

### `eximagent template save`
Save or update an email template. Variables (e.g. {{name}}, {{company}}) auto-extracted; attachment file-IDs auto-resolved.
  - `--businessType enum: export|import — Trade direction this template targets`
  - `--category — Category (e.g. 'cold-outreach', 'follow-up', 'sales')`
  - `--content (required) — Template body. Supports {{variable}} placeholders + [FILE_ID:storageId:filename] attachment markers.`
  - `--name (required) — Template name or id`
  - `--subject (required) — Subject line (may use {{variables}})`
  - example: `template save --name cold-intro --subject 'Partnership intro' --content 'Hi {{name}}, ...'`

### `eximagent trade lookup`
Search trade data: tariffs, duties, remedies, NTM measures by importer/exporter/product.
  - `--exporter (required) — Sending country — accepts country name, ISO2, ISO3 (e.g. "Viet Nam", "VN", "VNM")`
  - `--hsCode — HS code (2-10 digits, no separators)`
  - `--importer (required) — Receiving country — accepts country name, ISO2, ISO3 (e.g. "Germany", "DE", "DEU")`
  - `--limit — Max results (1-50, default 10)`
  - `--product — Product keyword (resolves to HS codes)`
  - `--type enum: all|duties|taxes|remedies|ntm — Filter to one data type`
  - exactly one of: `--hsCode` / `--product`
  - example: `trade lookup --exporter US --importer VN --hs-code 730441`
  - example: `trade lookup --from brazil --to us --product beef --type duties`

