@dench.com/cli 3.8.1-staging.390 → 3.8.2-staging.392
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/ads.ts +6 -2
- package/apps.ts +7 -3
- package/browser.ts +8 -3
- package/campaign.ts +1 -1
- package/chat.ts +6 -2
- package/cron.ts +7 -3
- package/dench.ts +52 -94
- package/image.ts +6 -2
- package/leads.ts +6 -2
- package/lib/command-args.ts +26 -9
- package/lib/command-help.ts +192 -39
- package/lib/file-command-contract.ts +106 -0
- package/lib/references.generated.json +3 -3
- package/meetings.ts +6 -2
- package/members.ts +6 -2
- package/package.json +1 -1
- package/search.ts +6 -2
- package/tools.ts +9 -5
- package/web.ts +20 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"revision": "
|
|
2
|
+
"revision": "cf2c231ad2fd37478c3bb18600aa896a7e22a629540d4341b2d95b613aa22a99",
|
|
3
3
|
"builtinHash": "a8028c61553799ccb761561a914bba50476c03c9d94f3ec4cb1ba0be560b4608",
|
|
4
4
|
"files": {
|
|
5
5
|
"SKILL.md": {
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"content": "---\nname: campaigns\ndescription: \"Build, run and fix email + LinkedIn outreach campaigns with `dench campaign` (CLI/API), starting from a raw brief: search people with exclusions, add them from a search, CSV, saved list or CRM view, write steps and personalized lines, preview messages, check launch readiness, launch, then track replies, the funnel and hold reasons. Use for any request to set up, edit, duplicate, delete or monitor an outreach, cold-email or LinkedIn campaign or sequence.\"\n---\n\n# Dench Outreach Campaigns\n\nAn **outreach campaign** sends a sequence of steps — emails from a connected\nGmail/Outlook mailbox, LinkedIn profile visits, connection requests and\nmessages from a connected LinkedIn account — to a list of people, one person\nat a time, inside a send window. The steps run in order, or as a workflow\nwhose If / else and Wait for them to accept steps send people down different\npaths. It is the Outreach → Campaigns page in the\nDench app, and everything that page does is also a `dench campaign …` command\nor an API call. Every command takes `--json`. Notices and cost lines go to\nstderr and stdout is plain JSON, so `> file.json` is safe.\n\nCampaign analytics scans the full history in bounded server-side pages. The\nJSON result includes `startedAt` and `computedAt`: totals reflect that\nobservation interval, not an atomic snapshot while sending continues. The\ndashboard refreshes every minute.\n\nNot this skill: one-shot newsletters and raw `dench email send` live in\n`email-campaigns`. Prospecting on its own (no campaign) is `sales` / `dench\nleads find`. A CRM record's history is `crm`.\n\n## Build a campaign from a brief\n\nThe brief can be an ICP document, a list of titles and companies, a sequence\nexported from another outreach tool, or one sentence. Follow these steps in\norder. Only step 3 (search), step 6 (AI lines) and step 9 (launch) spend money\nor send messages; say so before each one and wait for a yes before step 9.\n\n### 0. Check the prerequisites\n\n- **Campaign commands available.** Run `dench campaign help` and confirm it\n lists `draft`, `search`, `add`, `steps`, `person` and `preview`. An older CLI\n answers \"Unknown campaign subcommand: search\". Update with `npm install -g @dench.com/cli@latest`,\n or run commands as `npx -y -p @dench.com/cli@latest dench …`. The CLI comes\n preinstalled in a Dench chat sandbox. When testing a source checkout, check\n its campaign help: commands can be available before a release version bump.\n- **A workspace member's credentials.** `dench signin` (the human approves a\n link), a member-bound `DENCH_API_KEY`, or an agent session a member created.\n An organization-only key is refused with \"Outreach needs a workspace\n member's credentials\". Check with `dench context --json`.\n- **A paid workspace with credits.** Search and AI lines are billed to the\n workspace. `dench billing status --json` shows the balance; `dench upgrade`\n and `dench billing topup --amount 10` give the human a link.\n- **Connected senders.** `dench campaign accounts --json` must list a mailbox\n (for email steps) and/or a LinkedIn account (for LinkedIn steps). If a\n channel is empty, `dench tool connect gmail` (or `outlook`, `linkedin`)\n prints a sign-in link; the human opens it, then run `dench campaign\n accounts` again. Ids from this command go into `--email-mailboxes` and\n `--linkedin-accounts`.\n\n### 1. Turn the brief into a plan\n\nPull these out of the brief and ask the user only for what is missing:\n\n| Need | Where it goes |\n|---|---|\n| Who to reach: titles, seniority, industries, company size, places | search filters (step 3) |\n| Who to leave out: competitors, dev shops, current clients, wrong industries | exclusions (step 3) |\n| How many people | one campaign holds at most 300 (step 4) |\n| Channels and timing: which touches, how many days apart | `steps.json` (step 7) |\n| Branches: \"if they're connected…\", \"if they don't accept…\", \"if their title contains…\" | a workflow `steps.json` (step 7) |\n| What changes per person: first name, company, a role or reason to write | variables (steps 5 and 6) |\n| Timezone, days and hours, daily email cap, which accounts send | the draft (step 2) |\n| Buying signals (hiring, funding, launches, new leaders, post engagement) | the signal builder, below, instead of steps 3 and 4 |\n\nFilter names, exact values and ranges: `dench leads filters` (free) lists\nthem, `dench leads filters --filter industries --search \"financial\"` finds an\nexact industry name. Company size is a headcount bucket (`1-10`, `11-50`,\n`51-200`, `201-500`, `501-1000`, `1001-5000`, `5001-10000`, `10001+`); there is\nno revenue filter.\n\n### 2. Create the draft\n\n```bash\ndench campaign draft --name \"Fintech engineering leaders\" \\\n --request-id fintech-eng-leaders-q4 --timezone America/New_York \\\n --window \"Mon-Fri 9-17\" --daily-cap 30 \\\n --email-mailboxes <mailboxId> --linkedin-accounts <linkedinAccountId> --json\n```\n\n`--request-id` (8-64 characters) makes a retry return the same draft instead of\na second one. Without sender flags the draft sends from every running account\nyou own; the output says which. A draft sends nothing.\n\n### 3. Search, with exclusions\n\n```bash\ncat > filters.json <<'EOF'\n{\n \"titles\": [\"CTO\", \"VP Engineering\", \"Head of Engineering\"],\n \"industries\": [\"Financial Services\", \"Hospitals and Health Care\"],\n \"headcountRanges\": [\"51-200\", \"201-500\", \"501-1000\"],\n \"locations\": [\"United States\"]\n}\nEOF\n\ndench campaign search --filters @filters.json \\\n --not-industries \"IT Services and IT Consulting,Staffing and Recruiting\" \\\n --not-company-keywords \"software outsourcing\" \\\n --not-domains competitor.com,current-client.com \\\n --limit 25 --out audience.json\n```\n\n- **Cost: 1 cent per person returned**, billed on what comes back, not on the\n limit. The most a call can cost prints on stderr before it runs, and the\n result carries `estimatedCents` and `chargedCents`. There is no free preview;\n start with `--limit 25`, read the sample, then widen.\n- **Exclusions.** `--not-<filter>` (or `--exclude-<filter>`) works on every list\n filter, for example `--not-industries`, `--not-titles`, `--not-company-names`,\n `--not-company-keywords`; `--not-domains` is short for `--not-company-domains`.\n In JSON an exclusion is `{\"value\":\"Staffing and Recruiting\",\"exclude\":true}`\n inside that filter's list. A search still needs at least one include filter.\n- `--limit` is at most 100 per search. Continue with `--page-token\n <nextPageToken>` from the previous result (even after an empty page); keep the\n same filters.\n- `--out audience.json` writes the full result, complete profiles included, and\n the terminal shows a short summary. `--raw` prints the full result instead of\n writing a file. The file is checked before the search is billed: its folder\n must exist and the file must not (`--force` replaces it), so give each page\n its own name (`audience-2.json`). If the write still fails, the result prints\n on stdout with a `saveError` instead of being lost; do not run the search\n again.\n- Review before adding: `jq -r '.rows[] | [.name,.title,.company] | @tsv'\n audience.json`. Show the user 5-10 people and the cost so far. If the sample is\n wrong, change the filters and search again (each search is billed).\n\n### 4. Add the people\n\n```bash\ndench campaign add \"Fintech engineering leaders\" --people-file audience.json\n```\n\nThis adds the searched people without searching or paying again, up to 300\npeople per file, in batches of 50. New people become Leads in the CRM. The\nresult lists `added`, `duplicates`, `duplicatesElsewhere` (already queued or\nenrolled in another campaign, skipped unless `--allow-duplicates`), `suppressed`,\n`invalid`, `skippedNoIdentity`, `skippedMissingInfo` and `cappedAtLimit`. Read\nit back to the user.\n\nOther ways in, all with the same checks: `add --search '<filters json>'\n--limit 50` (search and add one page in a single billed call), `add --csv\npeople.csv`, `add --list <artifactId>`, `add --object lead --view \"<view>\"`,\n`add --name \"Ada\" --email ada@x.com`. See Add people below.\n\n- **A campaign holds 300 people**, counting stopped and finished ones. For a\n bigger audience, `dench campaign duplicate <campaign>` makes a new draft with\n the same steps, schedule, senders and lines and no people; add the next page\n of the search to the copy (`search --page-token …`). Duplicate checks include\n draft, active and paused campaigns, so someone queued or enrolled elsewhere\n is skipped.\n- People need an email for email steps and a LinkedIn profile URL for LinkedIn\n steps. A person without an email has their email steps skipped (launch warns);\n someone with neither is a launch blocker. Search results carry a LinkedIn URL\n and, when available, an email.\n- A mistake in a draft: `dench campaign remove <campaign> --person\n <enrollmentId|name>` takes a never-contacted person out and frees their place.\n\n### 5. Choose the variables\n\nA `{{token}}` or `[[token]]` in a subject or message is filled in per person.\nBoth forms work in email and LinkedIn messages, InMail subjects, previews and\nsends. Typing either opener brings up field suggestions, and choosing a field\nkeeps the brackets you started with:\n\n- **Fields of the person or their CRM row:** `{{First name}}`, `{{Company}}`,\n `{{Job Title}}`, `{{Website}}`, `{{Location}}` and any other column of the\n record.\n- **Values you supply.** In a `--people-file`, put them under `personalization`\n for each person: `{\"linkedinProfileUrl\":\"…\",\"personalization\":{\"fund\":\"Seed\n fund\",\"role_title\":\"Head of Data\"}}` makes `{{fund}}` and `{{role_title}}`. In\n a CSV every column that is not an email, LinkedIn URL or name becomes a value\n named after its header.\n- **Greeting names.** Built-in `name`, `firstName` and `lastName` are cleaned\n for greetings, so casing and credentials may change. Custom keys such as\n `first_name`, `Fund` and `role_title` keep the values you supply. To confirm\n an exact built-in name, use `dench campaign person set <campaign> --person\n <name> --field \"name=Hortensia Ruiz, MBA\"`.\n- **Fallbacks.** `{{First name || \"there\"}}` or `[[First name || \"there\"]]`.\n A plain token with no value for\n someone is a **launch blocker** for that person; a quoted fallback avoids it.\n Fix a single person before launch with `dench campaign person set\n <campaign> --person <name> --field \"Company=Acme\"`, or pull missing fields\n from their CRM record with `dench campaign sync <campaign>`.\n- **Brackets.** Write `{{First name}}` or `[[First name]]`, with a matching\n pair of double braces or square brackets. `steps set` refuses\n `{first_name}` or `{FIRST_NAME}`, which would go out exactly as typed; pass\n `--allow-single-braces` only when the braces are literal text.\n\n### 6. Optional: AI-written lines\n\nFor a line that must differ for every person (a reason to write, a comment on\ntheir company), define up to 5 AI-written lines. They cost about 3 cents a\nperson; say so, then:\n\n```bash\ncat > lines.json <<'EOF'\n{\n \"sells\": \"Senior engineers who join your team or own a build end to end\",\n \"lines\": [\n {\n \"label\": \"Why now\",\n \"instructions\": \"One sentence tying their company's business to engineering capacity.\",\n \"fallback\": \"building the roadmap faster\"\n }\n ]\n}\nEOF\ndench campaign personalize set \"Fintech engineering leaders\" --file lines.json\ndench campaign personalize get \"Fintech engineering leaders\"\n```\n\nUse the line in steps as `{{whyNow}}` (the key is made from the label; `get`\nprints it). Saving writes lines for up to 100 people right away; more waits\nfor `personalize write --yes`. Edit one person's line with `personalize edit`,\nredo it with `personalize rewrite`. Until a line is written the saved fallback\nis used and it never blocks launch.\n\n### 7. Write and check the steps\n\nFirst decide the shape. If everyone gets the same touches in the same order,\n`steps.json` is a plain JSON array in send order. If the brief says \"if …,\nthen …\" (connected or not, accepted or not, has an email or not, a field's\nvalue), it is a workflow: `{\"stepModel\":\"workflow\",\"steps\":[...]}` with\npaths (see Workflows below). Each step is one of `email`, `wait`,\n`linkedin_visit`, `linkedin_connection`, `linkedin_message`,\n`linkedin_like_post` or `linkedin_inmail`; a workflow can also use\n`condition` (If / else) and `wait_for_connection` (Wait for them to accept).\nTiming is a `wait` step between the\ntouches: wait hours = (day of this touch − day of the\nprevious touch) × 24. This example turns \"email day 1, LinkedIn connect day 2,\nLinkedIn message day 4 after they accept, email day 5 in the same thread,\nLinkedIn bump day 8, email day 10 in the same thread\" into steps:\n\n```json\n[\n {\n \"type\": \"email\",\n \"emailSubject\": \"Keep building while you hire\",\n \"emailTextBody\": \"{{First name || \\\"Hi\\\"}}, saw {{Company}} is hiring for {{role_title || \\\"engineering\\\"}}. We add senior engineers to your team or own a build end to end.\\n\\nWant me to send a few profiles?\"\n },\n { \"type\": \"wait\", \"waitHours\": 24 },\n {\n \"type\": \"linkedin_connection\",\n \"linkedinMessageBody\": \"{{First name || \\\"Hi\\\"}}, saw {{Company}} is hiring. We add senior engineers to your team on your hours. Open to connecting?\"\n },\n { \"type\": \"wait\", \"waitHours\": 48 },\n {\n \"type\": \"linkedin_message\",\n \"linkedinMessageBody\": \"Thanks for connecting, {{First name || \\\"there\\\"}}. Want me to send a few profiles that match your open roles?\",\n \"linkedinMessageMode\": \"wait_until_connected\",\n \"connectionTimeoutHours\": 336\n },\n { \"type\": \"wait\", \"waitHours\": 24 },\n {\n \"type\": \"email\",\n \"emailReplyToPrevious\": true,\n \"emailTextBody\": \"Following up on the {{role_title || \\\"engineering\\\"}} role. Can we find 15 minutes this week?\"\n },\n { \"type\": \"wait\", \"waitHours\": 72 },\n {\n \"type\": \"linkedin_message\",\n \"linkedinMessageBody\": \"Circling back, {{First name || \\\"there\\\"}}: worth 15 minutes this week?\",\n \"linkedinMessageMode\": \"skip_if_not_connected\"\n },\n { \"type\": \"wait\", \"waitHours\": 48 },\n {\n \"type\": \"email\",\n \"emailReplyToPrevious\": true,\n \"emailTextBody\": \"Last note from me. Want me to send a few profiles?\"\n }\n]\n```\n\n```bash\ndench campaign steps set \"Fintech engineering leaders\" --file steps.json\ndench campaign preview \"Fintech engineering leaders\" --person \"Ada Lovelace\"\n```\n\n`steps set` validates before it saves and refuses anything that would be\nsilently dropped or sent wrong (rules in Steps below). `preview` shows the exact\nmessages one person would get, with every token and AI line filled in, and\nsays what is missing or would be skipped; nothing is sent. Preview two or\nthree different people and show the user the text. In a workflow, `preview`\nshows the message of every path, and a person gets only the ones on the path\nthey take: say which path each message is on, or preview one with `--step N`.\n\nPost reactions, InMail and scheduled withdrawal of pending invitations are\nsupported; see the Steps section for their fields and limits. A plain list\nlike the one above runs every step in order for everyone; `onlyIf` on email\nand post-like steps and `linkedinMessageMode` adjust it to whether someone\nis connected. When the brief branches (\"if they are connected, message them;\nif not, send a connection request, wait 10 days for the accept and InMail the\nones who don't accept\"), write a workflow instead (Workflows under Steps):\neach step gets a `key` and says where it goes, and If / else (`condition`)\nand Wait for them to accept (`wait_for_connection`) steps choose the path.\nBefore saving, tell the user how you mapped the brief: each \"if\" and the\nstep that answers it, where the paths join again, where each one ends, and\nanything you changed because a workflow can't do it (a path that goes back\nto an earlier step, more than 20 steps). Dench also checks whether someone\nis already connected before sending an invitation.\n\n### 8. Check readiness\n\n```bash\ndench campaign launch \"Fintech engineering leaders\" --check --json\n```\n\n`--check` never launches. It lists each blocker with why it fires and the\ncommands that fix it, the warnings, the audience and step counts and the\n`fingerprint`, and exits 1 when blocked. Fix blockers (`steps set`, `add`,\n`remove`, `person set`, `set`) and run it again until it exits 0.\n\nThen show the user, in one message: the number of people and five examples, the\nsteps as rendered by `preview`, the schedule and timezone, the daily email cap,\nthe sending accounts, what has been spent, and that LinkedIn steps break\nLinkedIn's terms and can get the account restricted. Ask for a clear yes.\n\n### 9. Launch and watch\n\n```bash\ndench campaign launch \"Fintech engineering leaders\" --ack-linkedin-risk\ndench campaign get \"Fintech engineering leaders\"\ndench campaign replies \"Fintech engineering leaders\" --since 7d\n```\n\n`--ack-linkedin-risk` is required only when the campaign has LinkedIn steps and\nonly after the user accepts the notice. `launch` without `--check` launches as\nsoon as the campaign is ready. After launch:\n\n- `get` is the dashboard: funnel, what is sending now, what comes next and\n why anything is holding (hold reasons clear on their own; report the hold,\n not a failure). `events` is the activity log; `people --status replied` lists\n who answered; `replies` shows what they wrote; `pipeline` shows where people\n sit in the lead pipeline.\n- Change wording or waits in place with `steps edit`; stop a person with `stop`;\n `pause` and `resume` the whole campaign.\n- Replying to a prospect happens in the Inbox in the Dench app.\n\n### Fixing mistakes\n\n| Mistake | Fix |\n|---|---|\n| Added the wrong person (draft) | `dench campaign remove <c> --person <id>` |\n| Wrong email or LinkedIn URL (draft) | `dench campaign person set <c> --person <id> --email … --linkedin-url …` |\n| Typo in a message after launch | `dench campaign steps edit <c> --file steps.json` |\n| Step types, order or a workflow's paths must change after launch | `steps edit` keeps them fixed. `dench campaign duplicate <c>`, fix the copy, and add only people who were never contacted to it |\n| Person should stop getting messages | `dench campaign stop <c> --person <id>` |\n| Whole campaign is wrong | `dench campaign pause <c>`, or `archive` / `delete` (same thing: stops everyone, erases nothing) |\n| A search was too broad | Nothing refunds a search; narrow the filters, and check `estimatedCents` first |\n\n## Signal campaigns\n\nWhen the brief is \"reach companies that just hired, raised money, launched\nsomething, changed a leader, or whose people engage with a post\", build a signal\ncampaign instead of a one-off search:\n\n```bash\ndench campaign builder profile --json\ndench campaign builder autofill --description \"VP Engineering at US fintechs hiring AI engineers\" --json\ndench campaign builder save --file draft.json --json\ndench campaign builder build <draftId> --json\ndench campaign builder get <draftId> --json\ndench campaign builder opener <draftId> <leadId> --text \"Reviewed opening line\" --json\ndench campaign builder create <draftId> --name \"Hiring teams\" --json\ndench campaign sources get \"Hiring teams\" --json\ndench campaign sources signals \"Hiring teams\" --json\ndench campaign sources set \"Hiring teams\" --data '{\"status\":\"paused\",\"cadence\":\"weekly\"}' --json\ndench campaign sources preview \"Hiring teams\" --signal hiring --json\n```\n\n`draft.json` contains `clientRequestId`, `targeting`, `signals`, and optional\n`signalTerms`. Targeting contains `titles`, `locations`, `companySizes`,\n`industries`, `excludes`, and optional `companies`. Signals are `post_engagement`,\n`keyword`, `hiring`, `funding`, `launch`, or `new_leader`. `signalTerms` maps\n`post_engagement`, `keyword`, or `hiring` to string arrays.\n\nThe builder also exposes `terms --file file.json` (body `{ \"targeting\": ... }`)\nand `estimate --file file.json` (body with `kind` `people` or `week`, targeting\nand optional signals/terms). These use the same billable helpers as the UI.\n`build` starts asynchronous work; poll `builder get` before reviewing or\ncreating the campaign. `create` makes a draft campaign and does not launch it.\nActivating a saved source can schedule future billable tracking; it is separate\nfrom a one-time search audience.\n\n## Commands\n\n`<campaign>` is a campaign **id or its exact name** (case-insensitive). When\ntwo live campaigns share a name the command fails and lists both ids; use the\nid. `dench campaign list` shows ids.\n\n### Browse and inspect\n\n```bash\ndench campaign list [--all] [--json] # live campaigns; --all includes archived\ndench campaign get <campaign> [--json] # status, schedule, funnel, Now/Next, holds, people\ndench campaign people <campaign> [--status <s>] [--json]\ndench campaign events <campaign> [--limit 50] [--max-pages 1] [--json]\ndench campaign accounts [--json] # email + LinkedIn sender ids, warm-up, availability and capacity\ndench campaign preview <campaign> [--person <enrollmentId|name>] [--step N] [--json]\ndench campaign person show <campaign> --person <enrollmentId|name> [--json]\ndench campaign replies <campaign> [--person <enrollmentId|name>] [--since 7d] [--limit 20] [--json]\ndench campaign pipeline <campaign> [--json]\ndench campaign where <entryId> [--json]\n```\n\n`people --status` accepts an enrollment status: `draft` (not launched yet),\n`active`, `paused`, `finished` (reached the end: every step of a plain list, or\nthe end of their path in a workflow), `replied`, `stopped`,\n`bounced`, `complained`, `unsubscribed`, `failed`.\n\n`events` is the campaign's activity log, newest first: added, launched,\nemail sent, connection request sent, accepted the connection, LinkedIn\nmessage sent, profile visited, replied, step skipped, step failed, needs a\nlook, paused, resumed, stopped, finished, bounced, unsubscribed, campaign\ncompleted, campaign picked back up, campaign archived. `--limit` is the page size (1-200);\n`--max-pages` drains more pages. `--json` returns `{ events, isDone,\ncontinueCursor }`.\n\n`preview` renders every email and LinkedIn step (or step N) for one person\n(the first person when `--person` is left out) with fields and AI lines filled\nin. In a workflow that is every path's messages; the person gets only the ones\non their path. `person show` lists each step a person reached with its outcome (sent,\nopened, clicked, skipped and why, waiting, failed), when, and the subject and\nstart of the text that was sent. `replies` lists the people who replied, newest\nfirst, with the text of their latest inbound email or LinkedIn message (up to\n400 characters, quoted history removed) and whether you have answered.\n`pipeline` counts a campaign's people at each lead Status. `where <entryId>`\nlists the campaigns a CRM person or lead is in and their status in each.\n\n### Create and configure\n\n```bash\ndench campaign create --name \"<name>\" [--timezone <IANA tz>] \\\n [--window \"<spec>\" ...] [--daily-cap 1-100] [--stop-on-reply|--no-stop-on-reply] \\\n [--email-mailboxes id1,id2] [--linkedin-accounts id1,id2] \\\n [--start <ISO date/time>] [--end <ISO date/time>] [--json]\n\ndench campaign draft [--name \"<name>\"] [--request-id <8-64 chars>] [same settings as create] [--json]\n\ndench campaign set <campaign> [--name \"<new name>\"] [--timezone <tz>] [--window \"<spec>\" ...] \\\n [--daily-cap 1-100] [--stop-on-reply|--no-stop-on-reply] \\\n [--email-mailboxes id1,id2|none] [--linkedin-accounts id1,id2|none] \\\n [--start <ISO>|--clear-start] [--end <ISO>|--clear-end] [--json]\n\ndench campaign duplicate <campaign> [--name \"<name>\"] [--json]\ndench campaign archive <campaign> [--json] # stops everyone still in flight; final\ndench campaign delete <campaign> [--json] # the same command, named like Delete in the app\n```\n\n`create` and `draft` make the same kind of draft; `draft` is the retry-safe\none (a stable `--request-id`) and the name is optional.\n\n- `--timezone` defaults to the machine's timezone (UTC in a sandbox — pass\n the user's zone). With no `--window`, the campaign gets one window:\n Mon-Fri 9-18 in that timezone.\n- `--daily-cap` is the number of cold emails per day across the campaign\n (default 30, max 100). LinkedIn has its own per-account limits, set on the\n account, not here.\n- `--email-mailboxes` and `--linkedin-accounts` select **sender pools on the\n campaign**, not on individual steps. Each accepts up to 50 account ids,\n comma-separated or as a JSON array. Only accounts both the caller and the\n campaign owner may use are accepted. The app puts these under **Settings →\n Sender pools**, with remaining-today counts and availability.\n- Omit a sender flag to preserve that channel's current configuration. Pass\n `none` or `'[]'` to clear it. Legacy per-step senders remain until that\n channel's pool is explicitly configured. Each person's assigned sender\n stays fixed through their conversation, including after a pool is changed;\n disconnecting it does not silently move the conversation to another sender.\n- API equivalents: `emailMailboxAccountIds` and `linkedinAccountIds` on\n `POST /campaigns` and `PATCH /campaigns/{sequenceId}`. Launch readiness\n checks remain mandatory; selecting a sender does not bypass them.\n- `--stop-on-reply` (the default) stops a person's remaining steps the\n moment they reply on either channel. `--no-stop-on-reply` keeps sending.\n- `--start` / `--end` bound the campaign in time (ISO 8601). A bare date\n (`2026-09-03`) is midnight at the start of that day in the campaign's\n timezone (`--timezone`, or the campaign's own), so `--end 2026-10-01`\n means \"nothing goes out on or after Oct 1 local time\". Add a time and\n offset (`2026-09-03T09:00:00-07:00`) to be exact. `--clear-start` /\n `--clear-end` remove them.\n- `set` works for the life of the campaign (not once archived). Steps are\n the one thing `set` does not cover; see Steps below.\n- `set --timezone X` alone moves the existing windows to that zone.\n- `duplicate` makes a new draft named \"<name> copy\" (or `--name`) with the same\n steps, schedule, sender pools and AI lines, and no people.\n- `archive` and `delete` stop everyone still in flight and keep every lead and\n all activity; a campaign is never erased.\n\n#### `--window` spec\n\n`\"<days> <start>-<end>\"`, repeatable (up to eight), all in `--timezone`:\n\n| Spec | Meaning |\n|---|---|\n| `\"Mon-Fri 9-18\"` | weekdays, 09:00 until 18:00 |\n| `\"Mon,Wed,Fri 10-16\"` | those three days |\n| `\"Weekdays 9am-5pm\"` | am/pm accepted; `Weekdays`, `Weekends`, `Daily` are groups |\n| `\"Sat-Sun 10-14\"` | day ranges wrap (`Fri-Mon` = Fri, Sat, Sun, Mon) |\n\nHours are whole hours, 0-24; the end must be after the start. Minutes are\nnot supported.\n\n### Add people\n\n```bash\n# search filters: the flags, or --search/--filters JSON (same object, @file.json works)\ndench campaign search --filters @filters.json [--titles Founder,CEO] [--not-industries \"Staffing and Recruiting\"] [--limit 100] [--page-token <t>] [--out audience.json] [--raw]\ndench campaign add <campaign> --search '{\"titles\":[\"Founder\"],\"locations\":[\"United States\"]}' [--limit 50] [--page-token <t>] [--preview] [--allow-active] [--allow-duplicates] [--out added.json]\ndench campaign add <campaign> --titles Founder --locations \"United States\" [--headcount 11-50] [--not-domains example.com] [--limit 50]\n\n# people you already have\ndench campaign add <campaign> --people-file audience.json [--allow-duplicates]\ndench campaign add <campaign> --csv people.csv [--map \"email=Work Email\" ...] [--preview] [--allow-duplicates]\ndench campaign add <campaign> --list <artifactId> [--limit 100] [--enrichment enriched] [--preview] [--allow-duplicates]\n\n# CRM records\ndench campaign add <campaign> --object lead --view \"Q3 targets\" [--email-field Email] [--linkedin-field \"POC LinkedIn URL\"] [--name-field Name] [--allow-duplicates] [--json]\ndench campaign add <campaign> --object people --entries <id1>,<id2> [--relation-field Contact] [--reverse] [--json]\n\n# one person with no CRM row yet (a lead is created); needs --email, --linkedin-url or both\ndench campaign add <campaign> [--name \"Ada Lovelace\"] --email ada@example.com [--linkedin-url https://linkedin.com/in/ada] [--json]\n```\n\n**Search and filters.** `--filters` and `--search` take the same JSON with every\npeople filter and work on both `search` and `add`. The flags `--titles`,\n`--past-titles`, `--person-names`, `--locations`, `--seniorities`,\n`--past-seniorities`, `--profile-keywords`, `--company-keywords`,\n`--industries`, `--functions`, `--sub-functions`, `--company-domains`,\n`--company-names`, `--past-company-names`, `--company-hq`, `--company-types`,\n`--universities`, `--skills`, `--languages`, `--headcount`, the ranges\n`--years-in-role`, `--years-at-company`, `--days-since-job-change`,\n`--founded-years` (`min:max`), and `--exact` add to that JSON: list flags\nappend to the JSON lists, so exclusions in the JSON survive. `--not-<flag>` and\n`--exclude-<flag>` leave values out of any list filter. A filter name that is\nnot allowed lists the allowed ones and the nearest match. `dench leads filters`\nprints accepted values.\n\nA flag value is split at its commas: `--titles \"CTO,VP Engineering\"` is two\ntitles, and a Note on stderr shows how a value was split (not for domains or\nexact filter values such as seniorities). Names with a comma that match a known\nvalue stay whole (the industry \"Technology, Information and Internet\"). A\ntitle, place or company name that contains a comma must be wrapped in double\nquotes inside the shell quotes, `--titles '\"Vice President, Information\nSystems\"'` or `--not-company-names '\"Acme, Inc.\"'`, or go in the `--filters`\nJSON. Otherwise it becomes several values and widens a billed search.\n\n**Limits and cost.** `search` returns at most 100 people per call and `add\n--search` adds at most 50 (a higher `--limit` is capped, with a note). Search\nand `--preview` cost 1 cent per person returned; `add --search --preview` means\n**no enrollment**, not a free search. Adding with `--people-file`, `--csv`,\n`--list`, `--object` or a single person is free of search cost. Adding with a\nsearch into a campaign that is already active, paused or completed needs\n`--allow-active`, because new people start its steps right away. Every add\nresult carries `campaignStatus`.\n\n**`--people-file`** accepts a recipient array or the result of `campaign search`\n(its `recipients`), 1-300 people. A recipient may contain `email`,\n`linkedinProfileUrl` (also `linkedinUrl`, `linkedin`), `displayName` (also\n`name`, `fullName`) and `personalization` (merge values); other text columns\nbecome merge values named after the key. An unknown value type is refused with\nthe row number instead of being dropped. New identities become Leads.\n`--allow-duplicates` only disables the cross-campaign duplicate check, not\nsuppression or campaign-local deduplication.\n\n**`--csv`** reads a spreadsheet export the way the app's Add people does: the\nemail and LinkedIn columns are found from what they hold, whatever they are\ncalled; the name comes from First name + Last name columns or from one `Name`,\n`Full name` or `Contact` column, found by its header (that column also stays\navailable as a merge value); every other column becomes a merge value\n(`{{Company}}`, `{{Job Title}}`). `--map\nemail|linkedin|name|first|last=\"<header or column letter>\"` overrides a guess\n(`none` switches a field off); `name` is the whole name in one column and cannot\nbe combined with `first` or `last`. `--preview` shows how each column is read\nand adds nobody, and the `Read` note says when no name column was found. Comma,\nsemicolon and tab files work, up to 5 MB and 500 people.\n\n**`--list`** adds people from a saved list (`dench lists`, made by `dench leads\nfind --save-as`) without searching or paying again. New people become Leads\nduring enrollment, in batches of 50. Custom values saved under each person's\n`personalization` object are preserved, including `role_title` and `Fund`.\n`--enrichment` keeps only rows at that stage\n(`all`, `enriched`, `in_progress`, `no_contacts`, `failed`, `not_enriched`).\n\n**CRM records.**\n\n- One person needs at least one of `--email` / `--linkedin-url`. `--name`\n is only for this form; with `--object`, pick the name column with\n `--name-field`.\n- A campaign holds at most **300 people**. `add` stops at the cap and says\n so (`cappedAtLimit`). Adding works on drafts and on launched campaigns\n (new people start at step 1); not on archived ones.\n- `--view` reads the saved view's own filters, sort and pinned rows, the\n same rows `dench crm views entries <object> \"<view>\"` returns. A view\n over more than 1000 rows cannot be listed fully (`truncated`); the\n command warns and only the listed rows are considered — narrow the view.\n- Each record needs an email (`--email-field`, default the `Email`\n column) and/or a LinkedIn profile URL (`--linkedin-field`, default the\n LinkedIn URL column). Records with neither are skipped\n (`skippedNoIdentity`). `--relation-field Contact` follows a relation to\n the person record first; `--reverse` walks it the other way.\n- By default a person already **queued or enrolled in another draft, active or\n paused campaign** is skipped (`duplicatesElsewhere`); `--allow-duplicates`\n adds them anyway. Remove them from one draft's audience, archive an old draft,\n or stop their enrollment in a launched campaign to resolve the conflict.\n Someone already in this campaign is never added twice (`duplicates`; `merged`\n counts the ones that gained a missing email or URL). Unsubscribed or\n bounced people are skipped (`suppressed`).\n- The result says how many were added and why the rest were not. Read it\n back to the user before launching.\n\nEvery enrolled person **gets a lead**: a `lead` row is used as-is; a\n`people` row finds the lead whose `Contact` is that person or creates one\n(`Status = New`, `Source = Outbound`, `Contact` linked); a bare `--email` /\n`--linkedin-url` creates the lead too. Workspaces without a `lead` object\nstamp the person instead.\n\n### Steps\n\n```bash\ndench campaign steps get <campaign> [--with-ids] # JSON you can edit and save: an array, or {\"stepModel\":\"workflow\",\"steps\":[...]} for a workflow\ndench campaign steps set <campaign> --file steps.json [--allow-single-braces] [--json] # replaces every step; DRAFTS ONLY\ndench campaign steps edit <campaign> --file steps.json [--allow-single-braces] [--json] # text and waits in place, also after launch\n```\n\n`steps set` replaces every step and works only while the campaign is a draft.\n`steps edit` keeps the number, order and type of every step and changes their\ntext, waits, reactions, withdrawal settings and conditions, also after launch (people already waiting keep the\ntiming they started with; messages already sent, or already being prepared, keep\ntheir text). In `steps edit` each step in the file is its full new content:\nleaving out a subject (including `inmailSubject`), body, note, `onlyIf` or\n`emailReplyToPrevious` clears it. Senders, LinkedIn wait settings, reaction\nsettings and `withdrawAfterHours` are kept when omitted, and so are a workflow's paths and\nan If / else's settings; set `withdrawAfterHours: null` to turn withdrawal\noff. Steps match by order (a workflow's by `key`, so the file can keep the\norder it was written in), or by `stepId` from `steps get --with-ids` to edit\nonly some; a step saved since you read it is refused, not overwritten. After\nlaunch a workflow's paths (`key`, `next`, `yesNext`, `noNext`) and what an\nIf / else checks (`check`, `conditionField`, `conditionOperator`) stay fixed;\nits `conditionValue` and a `wait_for_connection`'s `connectionTimeoutHours`\ncan still change. On a draft, changing `check` or `conditionOperator` drops\nthe saved settings the new check doesn't use.\n\nFor a plain list, `steps.json` is a JSON array of step objects, in send\norder (max 20). A workflow wraps them as\n`{\"stepModel\":\"workflow\",\"steps\":[...]}` (see Workflows below). Each step\nhas a `type` and only the fields that type uses. For a campaign with sender\npools configured as above, omit step sender ids. A plain list:\n\n```json\n[\n { \"type\": \"linkedin_visit\" },\n {\n \"type\": \"linkedin_connection\",\n \"linkedinMessageBody\": \"Hi {{First name}}, enjoyed your post on {{Topic || \\\"AI\\\"}}.\"\n },\n { \"type\": \"wait\", \"waitHours\": 48 },\n {\n \"type\": \"linkedin_message\",\n \"linkedinMessageBody\": \"Thanks for connecting, {{First name}}. Quick question: …\",\n \"linkedinMessageMode\": \"wait_until_connected\",\n \"connectionTimeoutHours\": 336\n },\n {\n \"type\": \"email\",\n \"emailSubject\": \"Quick question, {{First name}}\",\n \"emailTextBody\": \"Hi {{First name}}, …\",\n \"onlyIf\": \"not_connected\"\n },\n { \"type\": \"wait\", \"waitHours\": 72 },\n {\n \"type\": \"email\",\n \"emailReplyToPrevious\": true,\n \"emailHtmlBody\": \"<p>Bumping this, {{First name}} — worth a look?</p>\"\n }\n]\n```\n\n| `type` | Required | Optional |\n|---|---|---|\n| `wait` | `waitHours` (> 0, up to 2160 = 90 days) | — |\n| `email` | `emailSubject` (unless `emailReplyToPrevious`); `emailHtmlBody` and/or `emailTextBody` | `emailReplyToPrevious` (thread under the last email sent to this person, no new subject); `onlyIf` |\n| `linkedin_visit` | — | — |\n| `linkedin_connection` | — | `linkedinMessageBody` (the invite note, ≤ 300 chars; the request goes out without it if LinkedIn refuses the note); `withdrawAfterHours` (168–2160, or `null` to turn off) |\n| `linkedin_message` | `linkedinMessageBody` (≤ 3000 chars) | `linkedinMessageMode`; `connectionTimeoutHours` (plain lists; a workflow uses `wait_for_connection`) |\n| `linkedin_like_post` | — | `likeReaction`; `likeMaxPostAgeDays`; `likeSkipPreviousWait`; `likeSkipNextWait`; `likeCount` (1–3 recent posts, default 1); `onlyIf` |\n| `linkedin_inmail` | `linkedinMessageBody` (≤ 1900 chars) | `inmailSubject` (≤ 200 chars; use this, not `emailSubject`) |\n| `condition` (If / else, workflows only) | `check`; `yesNext` (Yes) and `noNext` (No); with `check: \"field_value\"`, `conditionField`, `conditionOperator` and (except `is_empty` / `is_not_empty`) `conditionValue` | — |\n| `wait_for_connection` (Wait for them to accept, workflows only) | `yesNext` (Accepted) and `noNext` (Not accepted) | `connectionTimeoutHours` (the window: 24–2160 = 1–90 days, default 720) |\n\nIn a workflow every step also takes a `key` and a `next`; `condition` and\n`wait_for_connection` take `yesNext` and `noNext` instead of `next`.\n\nLegacy campaigns without a pool for a channel still require\n`emailMailboxAccountId` on email steps and `linkedinAccountId` on LinkedIn\nsteps, on Wait for them to accept (`wait_for_connection`) steps and on If / else\nsteps that check `linkedin_connected`. `steps get` / `steps set` preserve those\nvalues until conversion.\n\n- **What `steps set` and `steps edit` refuse** (so nothing is lost or sent\n raw): a field the step type would drop (`onlyIf` off an email or post-like step, a body on\n a visit, `emailReplyToPrevious` on the first email, condition fields its\n `check` or `conditionOperator` ignores), a missing subject or\n body, an invite note over 300, a message over 3000, an InMail body over 1900\n or InMail subject over 200 characters, out-of-range reaction/withdrawal/\n acceptance-window settings, `likeCount` outside 1–3,\n a wait of 0 or over 2160 hours, more than 20 steps, `{TOKEN}` with single\n braces, and workflow paths that don't hold together (Workflows below). It\n warns when several LinkedIn messages after a connection request each wait 30\n days for the accept, and when a plain list saved on a workflow campaign has\n a message after the connection request, which then no longer waits for the\n accept (Workflows below). An email with both `emailHtmlBody` and `emailTextBody`\n sends the HTML: when you edit only the text, the HTML copied from `steps get`\n is dropped (with a warning) and the new text is sent.\n- `onlyIf` — email and post-like steps in a plain list: `always` (default),\n `connected` (run only if the person accepted the LinkedIn connection),\n `not_connected` (send only if they have not). `connected` without an\n earlier `linkedin_connection` step is a launch warning. In a workflow,\n branch with an If / else (`\"check\": \"linkedin_connected\"`) instead.\n- `linkedinMessageMode` — plain lists: what a `linkedin_message` does while\n the invite is still pending: `wait_until_connected` (default when a\n `linkedin_connection` step comes before it) waits up to\n `connectionTimeoutHours` (1–2160 hours, default 720 = 30 days) then skips;\n `skip_if_not_connected` (default otherwise) skips right away. In a\n workflow a message never waits on its own (the default is\n `skip_if_not_connected`): put it on the Accepted path of a\n `wait_for_connection`, or the Yes path of a `linkedin_connected` If / else.\n- `withdrawAfterHours` — connection requests only. After 168–2160 hours\n (7–90 days), withdraw a still-pending invitation sent by this campaign.\n It never withdraws an unrelated invitation or an accepted connection.\n Omitted on creation or `null` means off; there is no automatic withdrawal\n default. A value of `600` means 25 days after the invitation was sent.\n- `likeReaction` — one of `like` (default), `celebrate`, `support`, `love`,\n `insightful`, `funny`. `likeMaxPostAgeDays` is a whole number from 1 to 365,\n default 30. `likeCount` (1–3, default 1) is how many posts to react to.\n The step chooses their newest eligible original posts and skips when none\n is available or (with one post) it was already reacted to. With\n `likeCount` above 1 a post already reacted to doesn't use one up, and\n fewer recent posts means fewer reactions.\n `likeSkipPreviousWait: true` checks for eligible posts before the wait\n immediately before this step starts. If there are none, it skips that wait\n and the like step, **keeping the following waits**, even when\n `likeSkipNextWait` is true. Earlier consecutive waits remain. Omitted means\n false, so existing campaigns keep their behavior. For example, Wait 2 days →\n Like → Wait 1 day → Message becomes Wait 1 day → Message when no eligible\n posts are found. New like steps created in the editor enable this option\n and set `likeSkipNextWait` to false.\n `likeSkipNextWait` defaults to `true` when omitted: **a reaction skipped\n during normal execution also skips\n consecutive wait steps immediately after it**, including a skip caused by\n `onlyIf`. Set it to `false` to preserve those waits. Omitting these fields\n in `steps edit` preserves their saved values.\n- InMail automatically skips people who are already connected. Do not add\n `onlyIf` or `linkedinMessageMode` to an InMail step. LinkedIn Premium InMail\n through the personal inbox is supported, not Sales Navigator or Recruiter\n InMail. Available InMail credits are checked; an open profile may be free.\n If InMail is unavailable or has no credits, the step skips rather than\n falling back to another send. Read the warnings from `launch --check`.\n- **Merge tokens** work in subjects, bodies and LinkedIn text:\n `{{First name}}`, `{{Company}}`, `{{Job Title}}` — any column of the\n enrolled record or value supplied with the person, plus `firstName`,\n `lastName`, `name`, `company`, `email`. Chain fallbacks with `||`:\n `{{First name || \"there\"}}`. A plain token with no value for a person is a\n **launch blocker** for that person (a quoted fallback avoids it); on a\n launched campaign such people are skipped at `add` time\n (`skippedMissingInfo`). AI-line tokens use their saved fallback until the line\n is written and never block launch.\n\n#### Workflows (branching paths)\n\nA workflow is a campaign whose steps say where each one goes, so different\npeople take different paths through the same campaign (the app shows it as\nthe campaign's Sequence tab). Use one whenever the brief has an \"if\".\n\n**How the Sequence tab draws them.** Each ordinary `wait` is a separate,\nclickable step (for example, \"Wait 2 days\") between actions. Add one from\n**+ → Flow → Wait**, then click it to edit the amount and unit. Waits can\nalso go before End; deleting an action keeps the wait before it.\nA `linkedin_connection` card carries the `wait_for_connection` right after\nit (**Wait up to** N days, paths Accepted / Not accepted) and, when the only\nway into the request is the No exit of a `linkedin_connected` If / else,\nthat check too (**If already connected → Separate steps for existing\nconnections**, a third path).\nThe acceptance window stays on the connection request; the add menu does\nnot offer a separate Wait for them to accept step. The stored steps below\nkeep the same timing and paths.\n\n**Keyboard editing in a draft.** Select a node and press Delete or Backspace\nto remove it; a branching node asks which path to keep. Use **⌘Z** to undo and\n**⌘⇧Z** to redo (**Ctrl+Z** / **Ctrl+Shift+Z** or **Ctrl+Y** on Windows).\nUndo restores the node's content and connections, including after deleting\nthe last node. In a message or other text field, the shortcuts edit the text\nnormally. Sequence history is kept for the current campaign editing session.\n\n**Keys and paths.** Give every step a `key`: a short id like `\"invite\"`,\nunique in the campaign, never `\"end\"`. Then say where it goes:\n\n- `condition` (If / else) and `wait_for_connection` (Wait for them to accept)\n have two exits, both required: `yesNext` and `noNext` (Yes / No, or\n Accepted / Not accepted). They take no `next`.\n- Every other step has one exit, `next`. Left out, it is the step listed\n after it in the file, or `\"end\"` after the last one.\n- An exit is another step's `key`, or `\"end\"`: that path stops there and the\n person is done with the campaign. Any path can end anywhere, so one branch\n can stop after an InMail while the other goes on.\n- **Merges**: several exits may point at the same step. Write a message once,\n then point the If / else's Yes exit and the Wait for them to accept step's\n Accepted exit at it; everything after it is shared.\n\n**If / else** (`\"type\": \"condition\"`) sends nothing. It checks the person\nwhen they reach it and takes Yes or No (`check`):\n\nIn the editor, a new If / else starts with **Choose a condition**. Select a\ncheck in its settings; the unfinished step can be saved as a draft, but a\ncondition must be chosen before launch.\n\n| `check` | Yes when |\n|---|---|\n| `linkedin_connected` | they are connected to the sender on LinkedIn (No without a LinkedIn profile) |\n| `has_email` | they have an email address that can still be emailed (not bounced, unsubscribed or marked as spam) |\n| `opened_last_email` | they opened the last email sent to them. Needs an email before it on every path and open tracking; put a wait between the email and the check so people have time to open it (`launch --check` warns otherwise) |\n| `field_value` | a field on their lead matches (Field value): `conditionField` (the field's name, like `\"Job Title\"` or `\"Source\"`), `conditionOperator` and, except for the empty checks, `conditionValue` |\n\nField value reads the person's lead, or their own record if they have no\nlead, when they reach the If / else. The campaign itself changes `Status`,\n`Last touch` and `LinkedIn connection` as it runs (How it shows up in the CRM,\nbelow): after the first send everyone is `Contacted`, so don't branch on\nthose to mean \"before we wrote to them\". Pick a field the campaign doesn't\nwrite.\n\nField value operators (`conditionOperator`), all case-insensitive:\n\n| `conditionOperator` | Yes when the field |\n|---|---|\n| `is` | equals `conditionValue` (on a multi-value field: any of its values does) |\n| `is_not` | does not equal it (none of its values do) |\n| `contains` | contains it as text (any of its values does) |\n| `is_empty` | has no value |\n| `is_not_empty` | has a value |\n\n**Wait for them to accept** (`\"type\": \"wait_for_connection\"`) sends nothing.\nFrom when the person reaches it, it waits up to `connectionTimeoutHours`\n(the window: 24–2160 hours = 1–90 days, default 720 = 30 days) for the\nconnection request to be accepted. It takes Accepted as soon as they accept\n(right away if they were already connected) and Not accepted when the window\ncloses, the invitation is withdrawn or it never went out. It needs a\n`linkedin_connection` before it on every path. Keep that request's\n`withdrawAfterHours` longer than the window, or leave it off: a withdrawal\nduring the wait ends it as Not accepted.\n\n**Rules.**\n\n- Paths only move forward: no loops, no exit back to an earlier step, and\n every step reachable from the first. At most 20 steps, If / else and Wait\n for them to accept included.\n- The server stores the steps in path order, which can differ from the\n file's; the paths, not the order, decide who gets what. `steps get` shows\n the stored order and `dench campaign get` lists each step's paths.\n- A reply-in-thread email (`\"emailReplyToPrevious\": true`) needs an email\n before it on every path that reaches it; it threads under the last email\n sent to that person.\n- Branch with If / else and Wait for them to accept, not `onlyIf` and\n `linkedinMessageMode`: in a workflow a `linkedin_message` never waits for\n the accept on its own. A plain list file (no keys or paths) saved on a\n workflow campaign runs as a workflow too, so a message after its connection\n request skips anyone who hasn't accepted yet: edit the file `steps get`\n prints instead. `steps set` warns about such a list, and `launch --check`\n about any message some path reaches after a connection request without a\n Wait for them to accept or a `linkedin_connected` If / else.\n- Steps with a `key`, a path or one of these two types save the campaign as\n a workflow, and a workflow stays one (`\"stepModel\": \"linear\"` is refused).\n For a plain list again, start a new campaign with `dench campaign draft`\n and set the steps there. Don't use `dench campaign duplicate`: the copy is\n still a workflow, so a plain-list `linkedin_message` would no longer wait\n for the accept. `steps get` prints a workflow\n as `{\"stepModel\":\"workflow\",\"steps\":[...]}`; `steps set` and `steps edit`\n take that file back as is.\n- After launch the structure is fixed: step types, keys, paths (`next`,\n `yesNext`, `noNext`) and what each If / else checks (`check`,\n `conditionField`, `conditionOperator`). `steps edit` still changes text,\n waits, `conditionValue` and the window of a Wait for them to accept. To\n change the structure, `dench campaign duplicate <c>`, fix the copy and add\n only people who were never contacted.\n\n**Workflow launch blockers** (`launch --check` names the step):\n\n| Blocker | What's wrong | Fix |\n|---|---|---|\n| `workflow_path_invalid` | the paths don't hold together: two steps share a key, an exit names a key no step has, a path goes back or loops, nothing leads to a step, or an If / else or Wait for them to accept misses an exit. Also fires when a plain list holds one of those two steps | fix `key`, `next`, `yesNext` and `noNext` in the file, then `steps set` |\n| `wait_for_connection_without_invite` | a path reaches a Wait for them to accept with no connection request before it | add a `linkedin_connection` earlier on that path |\n| `opened_check_without_email` | a path reaches an `opened_last_email` If / else with no email before it | send an email earlier on every path to it, or check something else |\n| `open_tracking_unavailable` | email opens aren't tracked, so `opened_last_email` would always say No | check something else |\n| `condition_field_missing` | a `field_value` If / else names a field the campaign's leads don't have | `dench crm fields list lead` (for people with no lead, the object they were added from, like `dench crm fields list people`), then use one of those field names |\n| `reply_without_prior_email` | a path reaches a reply-in-thread email with no email before it | send an email earlier on every path to it, or give it its own `emailSubject` |\n\n**Example: connected or not, an acceptance window and an InMail fallback.**\nPeople already connected get the message right away. Everyone else gets a\nconnection request (withdrawn after 25 days if still pending); those who\naccept within 10 days get the same message, the rest get an InMail and\nstop. After the message: a 2-day wait, a reaction to two recent posts, a\nday, then a follow-up. Supply `Fund` for each person (or use a deliberate\nfallback); do not guess investor data. Sender pools still come from the\ncampaign settings.\n\n```json\n{\n \"stepModel\": \"workflow\",\n \"steps\": [\n {\n \"key\": \"connected\",\n \"type\": \"condition\",\n \"check\": \"linkedin_connected\",\n \"yesNext\": \"pitch\",\n \"noNext\": \"invite\"\n },\n {\n \"key\": \"invite\",\n \"type\": \"linkedin_connection\",\n \"withdrawAfterHours\": 600,\n \"next\": \"accept\"\n },\n {\n \"key\": \"accept\",\n \"type\": \"wait_for_connection\",\n \"connectionTimeoutHours\": 240,\n \"yesNext\": \"pitch\",\n \"noNext\": \"inmail\"\n },\n {\n \"key\": \"inmail\",\n \"type\": \"linkedin_inmail\",\n \"inmailSubject\": \"Fellow {{Fund}} founder\",\n \"linkedinMessageBody\": \"Hi {{First name}}, fellow {{Fund}} founder here. We wrote a short report on how teams like {{Company}} handle this. May I send it?\",\n \"next\": \"end\"\n },\n {\n \"key\": \"pitch\",\n \"type\": \"linkedin_message\",\n \"linkedinMessageBody\": \"Hi {{First name}}, fellow {{Fund}} founder here. We wrote a short report on how teams like {{Company}} handle this. May I send it?\",\n \"next\": \"pause\"\n },\n { \"key\": \"pause\", \"type\": \"wait\", \"waitHours\": 48, \"next\": \"like\" },\n {\n \"key\": \"like\",\n \"type\": \"linkedin_like_post\",\n \"likeReaction\": \"like\",\n \"likeMaxPostAgeDays\": 30,\n \"likeCount\": 2,\n \"next\": \"pause2\"\n },\n { \"key\": \"pause2\", \"type\": \"wait\", \"waitHours\": 24, \"next\": \"report\" },\n {\n \"key\": \"report\",\n \"type\": \"linkedin_message\",\n \"linkedinMessageBody\": \"Hi {{First name}}, is the report useful for {{Company}}?\",\n \"next\": \"end\"\n }\n ]\n}\n```\n\nExplain the mapping to the user like this: \"if connected\" is the\n`connected` If / else; \"else a connection request\" is its No exit to\n`invite`; \"wait 10 days\" is `accept` (`connectionTimeoutHours: 240`); its\nAccepted exit merges into the same `pitch` as the Yes exit; Not accepted goes\nto `inmail`, which ends. Everything from `pitch` on is one shared path. In\nthe app that is one \"Send connection request\" card with Already connected,\nAccepted and Not accepted paths and \"Wait up to 10 days\"; the 2-day and\n1-day waits appear as separate steps before the Like and follow-up cards.\n\nSave on a draft, inspect the round trip, then check readiness without sending:\n\n```bash\ndench campaign steps set <campaign> --file steps.json --json\ndench campaign steps get <campaign> --with-ids\ndench campaign launch <campaign> --check --json\n```\n\nThe REST operations `campaigns.steps.replace` and `campaigns.steps.update` use\nthe same field rules. When replacing a workflow's steps, send\n`\"stepModel\": \"workflow\"` so they are checked the way they will be saved.\nUpdates require `stepId`, `type` and `expectedUpdatedAt`; only supplied fields\nchange. Defaults are not re-applied to a partial update.\nKeep the campaign at a reviewed draft until the user approves real sends.\n- **Account ids.** `dench campaign accounts --json` returns `mailboxes` with\n `emailMailboxAccountId` and `linkedinAccounts` with `linkedinAccountId`.\n Both lists honor the acting member's connection visibility: their own\n private accounts and teammates' shared accounts, never someone else's\n private account. LinkedIn discovery also excludes removed accounts,\n former members' accounts and mock connections. It needs a live member's\n API key or agent session; an organization-only key is refused.\n LinkedIn summaries contain display/owner labels, status, availability,\n `remainingToday` (`invites`, `messages`, `profiles`) and\n `remainingInvitesThisWeek`. Today's invites also respect the rolling\n seven-day limit. Mailbox summaries include warm-up status, when a new\n mailbox is ready and its daily cold-send cap. These are account-local usage\n snapshots before queued work and campaign limits, not send reservations. Pool\n updates validate both the editor's and campaign owner's access; launch and\n dispatch still validate availability. Use only the Dench account ids in a\n campaign pool.\n API equivalent for LinkedIn discovery: `GET $DENCH_API_BASE/campaigns/accounts/linkedin`\n with the same bearer credentials; no caller-supplied user or workspace id.\n\n### AI-written lines\n\n```bash\ndench campaign personalize get <campaign>\ndench campaign personalize set <campaign> --file lines.json\ndench campaign personalize write <campaign> [--all] [--yes]\ndench campaign personalize edit <campaign> --person <enrollmentId|name> --line <key> --text \"…\"\ndench campaign personalize rewrite <campaign> --person <enrollmentId|name> [--line <key>]\ndench campaign personalize clear <campaign>\n```\n\nUp to 5 lines per campaign, about 3 cents a person. `lines.json` is\n`{\"sells\":\"…\",\"lines\":[{\"label\",\"instructions\",\"fallback\"}]}`; optional per\nline: `key` (otherwise made from the label), `maxChars` 20-300 and `fallbackFrom`\n(a field to use before the fallback). `set` writes lines for up to 100 people\nright away; more waits for `write --yes`, which confirms the cost. `write --all`\nrewrites everyone; `edit --text \"\"` undoes a manual edit. `clear` turns lines\noff; take the `{{keys}}` out of the steps first.\n\n### Per person\n\n```bash\ndench campaign stop <campaign> --person <enrollmentId|name> [--json] # nothing more is sent to them\ndench campaign remove <campaign> --person <enrollmentId|name> [--json] # drafts only: drop someone never contacted\ndench campaign resume-person <campaign> (--person <enrollmentId|name> | --all) [--json]\ndench campaign person set <campaign> --person <enrollmentId|name> [--email ada@new.com] [--linkedin-url <url>] [--field \"Job Title=CTO\" ...] [--json]\ndench campaign sync <campaign> [--json] # fill empty email, LinkedIn URL, name and fields from CRM records\n```\n\nEnrollment ids come from `dench campaign people`. A person's name works\nwhen it is unique in the campaign. `stop` on a never-contacted person in a\ndraft removes them, which frees their place in the 300-person cap. `remove`\nrefuses anyone already contacted. `resume-person --all` resumes every paused\nperson in an active campaign, for example after a sender was reconnected.\n`person set` fixes an address or fills a `{{merge field}}` before launch (a\ncorrected email, LinkedIn URL or matching column also updates the linked CRM\nrecord); `--field` repeats. `sync` fills each unfinished person's empty email,\nLinkedIn URL, name and merge fields from their linked CRM record, for example\nafter enriching the records; it never overwrites a value the campaign already\nhas.\n\n### Launch and lifecycle\n\n```bash\ndench campaign launch <campaign> --check [--json] # readiness only; never launches\ndench campaign launch <campaign> [--ack-linkedin-risk] [--json]\ndench campaign pause <campaign> [--json]\ndench campaign resume <campaign> [--json]\ndench campaign archive <campaign> [--json] # stops everyone still in flight; final\n```\n\n`launch --check` runs only the readiness check and never launches. It\nprints each blocker with why it fires and the commands that fix it\n(`reasons` and `fix` on every entry of `blockers[]` in `--json`), the\nwarnings, the audience and step counts and the `fingerprint`, then exits 0\nwhen the campaign is ready and 1 when it is blocked. Run it before every\nlaunch: plain `launch` launches as soon as the campaign is ready, so it is\nnot a dry run. `start` is another name for `launch`, including `--check`.\n\n`launch` runs the same check first. When something is missing it\nprints every blocker in plain words — no steps, nobody enrolled, a step's\nmailbox or LinkedIn account missing or disconnected, people without an\nemail or URL, people missing a merge field the steps use, a reply-to-thread\nstep with no earlier email, people enrolled in another LinkedIn campaign —\nand exits 1 without launching. Fix them (`steps set`, `add`, `set`) and run\n`launch` again. `--json` prints the full readiness payload (`blockers[]`\ngrouped by problem with the affected people, `warnings[]`).\n\nCampaigns with LinkedIn steps need `--ack-linkedin-risk`: automated\nLinkedIn activity is against LinkedIn's terms and can get the account\nrestricted. Say so to the user and pass the flag only once they accept.\n\nStatus moves `draft → active → (paused ↔ active) → completed`, and\n`archived` from anywhere. `pause` only acts on an active campaign, `resume`\nonly on a paused one; both say when there is nothing to do. `completed`\nmeans everyone finished, replied or stopped; adding people to a completed\ncampaign revives it.\n\n## Reading `dench campaign get`\n\n```\nFounder outreach — active [js7…]\nSchedule: Mon-Fri 9-18 America/Los_Angeles · up to 30 emails/day · stops on reply\nSteps (4): Visit profile → Connect on LinkedIn → Wait 2d → Email\n\nFunnel\n Enrolled 120\n In flight 38 (active 36, paused 2)\n Contacted 80\n LinkedIn invited 60 · accepted 22 · messaged 18\n Email sent 75 · opened 30 · clicked 4\n Replied 9 (11.3% of contacted)\n Done finished 30 · stopped 5 · failed 1\n\nNow / Next\n Now: Sending a connection request to Grace Hopper (since Sep 2, 2026, 10:41 AM)\n Next: Send an email to Ada Lovelace at Sep 2, 2026, 10:44 AM (in 3m)\n Held: today's email cap is reached · resumes Sep 3, 2026, 9:00 AM (in 22h)\n Emails today: 30/30 · next email Sep 3, 2026, 9:00 AM (in 22h)\n Sender Mark R: running · invites 12/25 today, 40/100 this week · messages 3/40 today · 09:00-18:00 America/Los_Angeles (in window)\n\nPeople (50 of 120)\n Ada Lovelace — active · waiting: email · until Sep 2, 2026, 10:44 AM (in 3m) · last: invite sent 2d ago · lead: Contacted [enr_…]\n Grace Hopper — active · sending: connect on linkedin · hold: the sender's daily LinkedIn limit is reached (until …) [enr_…]\n Linus T — replied · last: LinkedIn message sent 5h ago · lead: Replied [enr_…]\n```\n\n- **Funnel**: `Enrolled` = everyone ever added. `Contacted` = got at least\n one email, invite or message (a profile visit does not count). `Replied`\n counts replies from people this campaign contacted, whether or not the\n reply stopped them; the rate is replied / `Contacted`. `Done` splits\n finished (reached the end), stopped (stopped, bounced, complained,\n unsubscribed) and failed. In a plain list a finished person ran every\n step; in a workflow they reached the end of their path, so someone on the\n Not accepted → InMail path finishes without the steps on the other path.\n- **Now** is the one action being sent this instant; **Next** is the next\n action and when. `Held` explains why nothing is going out.\n- **Hold reasons** (on `Next`, on a person, or as `Held`), in plain words:\n the campaign is paused · a sending account is disconnected · today's email\n cap is reached · outside the sender's working hours · outside the\n campaign's send window · the sender's daily LinkedIn limit is reached ·\n the sender's weekly connection limit is reached · spacing out sends so\n they look human · checking whether they are already connected · paused\n after LinkedIn errors · the LinkedIn account is paused · LinkedIn asked\n us to retry later · writing personalized lines (shown on a person as\n `note: Waiting for personalized lines`; after 2 hours the saved fallback\n text is sent). Every one of these clears on its own; none means a\n step \"did not send\". Report the hold, not a failure.\n- **People**: status, what their current step is doing, the last delivered\n step, any hold, and the linked lead's `Status`. `--json` returns `{\n detail, analytics, live }` — the raw campaign (`detail.enrollments`,\n `detail.currentRuns[enrollmentId].hold`, `detail.lastTouches`,\n `detail.connectedEntries[enrollmentId].lead`), the funnel and the live\n strip.\n\nA workflow's `get` lists its paths, counts each exit and shows the\nacceptance waits:\n\n```\nSteps (9, a workflow with paths):\n 1. If / else: Connected on LinkedIn? → Yes: LinkedIn message (step 5) · No: Connect on LinkedIn (step 2)\n 2. Connect on LinkedIn\n 3. Wait for them to accept (up to 10d) → Accepted: LinkedIn message (step 5) · Not accepted: LinkedIn InMail (step 4)\n 4. LinkedIn InMail → End\n 5. LinkedIn message\n …\n\nFunnel\n …\n Done finished 3 · stopped 0 · failed 0\n Paths step 1 If / else: Connected on LinkedIn? → Yes 12 · No 40\n step 3 Wait for them to accept → Accepted 9 · Not accepted 14\n\nNow / Next\n Next: Wait for Ada Lovelace to accept the connection request — next check Sep 2, 2026, 12:41 PM (in 2h), stops waiting Sep 7, 2026, 10:41 AM (in 5d)\n\nPeople (50 of 52)\n Ada Lovelace — active · waiting: Wait for them to accept · stops waiting Sep 7, 2026, 10:41 AM (in 5d) [enr_…]\n Grace Hopper — active · checking: If / else: Connected on LinkedIn? [enr_…]\n```\n\n- **Steps**: one line per step in the stored order. A step names where it\n goes only when that isn't the next line; `→ End` ends that path. If /\n else and Wait for them to accept name both exits.\n- **Paths**: how many people have taken each exit so far (`branchStats` in\n `--json` analytics). The two numbers can add up to less than `Enrolled`:\n people before the step, or on another path, haven't reached it.\n- **Next** for a Wait for them to accept: when Dench next checks for the\n accept, and when it stops waiting and takes Not accepted. `Check\n \"<label>\" for <name>` is an If / else about to run.\n- **People**: `checking: <If / else>` while an If / else or Wait for them to\n accept reads the profile (nothing is sent); `waiting: Wait for them to\n accept · stops waiting …` until they accept or the window closes.\n\n## How it shows up in the CRM\n\n- **Lead `Status` moves on its own, forward only**: the first delivered\n email, connection request or LinkedIn message moves New → `Contacted`; a\n reply moves it to `Replied`. Nothing sets `Qualified` / `Disqualified` or\n moves backwards, and a `Status` outside New/Contacted/Replied is left\n alone. `Last touch` is bumped on every send, acceptance and reply. Never\n hand-edit `Status` after a step ran — you would race the writeback.\n- **Lead `LinkedIn connection` tracks the relationship, forward only**: a\n connection request sets `Invited`; an acceptance — or finding the person\n is already a connection — sets `Connected`. The column is created on the\n lead object the first time a LinkedIn step runs; email-only campaigns\n never touch it.\n- Every touch is also an event on the lead's History tab (source\n \"Campaign\") and in `dench campaign events`. `dench crm history <object>\n <entryId>` shows the field changes under the actor `Campaign: <name>`.\n- Enrolling a `people` row links its lead's `Contact` to that person; that\n link is expected — leave it.\n\n## REST API and app SDK\n\nAll paths are under `/api/v1` and need member-bound credentials. Agents can call\nany operation through `dench.api.call(operationId, args)`, and the typed\nmethods are under `dench.campaigns` (see [APPS.md](./APPS.md)). The full\nrequest and response schemas are in [API.md](./API.md) and `openapi.json`.\nOperation ids below are `campaigns.<name>`:\n\n| What | Operations |\n|---|---|\n| Browse and inspect | `list`, `get`, `analytics`, `live`, `events`, `preview`, `pipeline`, `people.timeline`, `people.forRecord`, `accounts.email`, `accounts.linkedin` |\n| Create and configure | `create`, `drafts.create`, `update`, `duplicate`, `archive` |\n| Add people | `audience.search`, `people.addFromSearch`, `people.addBatch` (50 recipients), `people.addFromCrm` (100 ids), `people.add`; saved lists via `lists.rows` and `people.addBatch`, retaining each row's `personalization` |\n| Steps | `steps.replace` (draft), `steps.update` (also after launch) |\n| AI lines | `personalization.get`, `.set`, `.write`, `.edit`, `.rewrite`, `.clear` |\n| Per person | `people.stop`, `people.remove`, `people.resume`, `people.resumeAll`, `people.setEmail`, `people.setLinkedin`, `people.setField`, `people.sync` |\n| Launch | `launch.preview` (`GET /campaigns/{sequenceId}/launch`, returns the `fingerprint`), `launch` (`POST`, needs that `fingerprint`), `pause`, `resume` |\n| Signals | `builder.profile`, `.save`, `.get`, `.build`, `.create`, `.opener`, `.autofill`, `.terms`, `.estimate`; `sources.get`, `.signals`, `.update`, `.preview` |\n\nSearch bodies are `{ \"filters\": { … }, \"limit\": 50 }` with an optional\n`pageToken`; batch bodies are `{ \"recipients\": [ … ] }`. There is no reply-text\nendpoint: combine `campaigns.get` with the CRM activity of the person, or use\n`dench campaign replies`.\n\n## Rules\n\n- `steps set` works only in draft; `steps edit` changes text and waits after\n launch. Schedule, daily cap, stop-on-reply, start and end change any time\n before archive.\n- One campaign, one schedule: all steps share the send windows; LinkedIn\n steps also respect the LinkedIn account's own working hours and limits.\n- Max 300 people per campaign, 20 steps, 100 ids per CRM `add` batch and 50\n people per search or file batch (the CLI chunks for you), 8 send windows,\n 300 characters per invite note, 3000 characters per LinkedIn message.\n- Outreach needs a **workspace member's** credentials: a `dench signin`\n session, a member-bound `DENCH_API_KEY`, or an agent session a member\n created. With an organization-only key every command fails with\n \"Outreach needs a workspace member's credentials\". That is about whose\n credentials the run has, not about sending accounts — connecting a\n mailbox changes nothing. Ask the user to run it from a chat in the Dench\n app (or to link their account in Slack) so the run carries their\n identity, rather than retrying.\n- `dench email multichannel …` drives the same campaigns with fewer\n controls; prefer `dench campaign`.\n- HTTP equivalent: `$DENCH_API_BASE/campaigns…` (the Campaigns section of\n `references/API.md`; operationIds `campaigns_*` in `openapi.json`). `launch` there needs the `fingerprint` from\n `GET /campaigns/{sequenceId}/launch`.\n\n## What still needs the user in the Dench app\n\n- Signing in to a mailbox or LinkedIn account (`dench tool connect` prints the\n link), reconnecting or disconnecting it, and a mailbox's warm-up sign-in.\n- A mailbox's daily cold-send cap, skipping its warm-up and sharing it with\n teammates; a LinkedIn account's daily and weekly limits, working hours and\n risk acknowledgement.\n- Answering replies in the Inbox.\n"
|
|
32
32
|
},
|
|
33
33
|
"references/CLI.md": {
|
|
34
|
-
"sha256": "
|
|
35
|
-
"content": "# Dench CLI\n\nGenerated from the command registry and actual saved-setup parsers. Run `dench <command> --help` for flags. API-only upload phases and compatibility aliases are not standalone commands.\n\nUse `dench context --sections organisation,user,memory` for knowledge, `dench memory` for facts, `dench env` for explicitly saved executable setups, and `dench secrets` for encrypted credentials. `dench cron secret` signs inbound webhooks; it does not store service credentials.\n\n## Reusable setups\n\n```text\ndench env — verified setups reused across chats\n\n dench env list [--limit N] [--cursor CURSOR] [--json]\n dench env show NAME [--revision SHA256] [--json]\n dench env save NAME --path DIRECTORY [--wait SECONDS | --no-wait] [--json]\n dench env run NAME [--revision SHA256] [--state-key KEY] [--prepare-local] [--json] -- COMMAND [ARGS...]\n dench env delete NAME [--json]\n\nSave requires dench-env.json, SKILL.md, an explicit files allowlist and verify\ncommands. Credentials, caches and symlinks are excluded from source uploads.\nSave returns an authoritative preparation receipt; default observation is 10s.\nReady runs reuse prepared dependencies. --prepare-local explicitly prepares a\nseparate local platform cache once; ordinary ready runs never reinstall.\nState is job-local by default; choose an explicit state key to share it.\nExample: dench env run openai-report -- node report.mjs\n```\n\n```text\ndench secrets — workspace-shared encrypted credentials\n\n dench secrets set NAME --stdin [--kind env|file] [--expected-version N] [--json]\n dench secrets set NAME --file PATH [--kind env|file] [--expected-version N] [--json]\n dench secrets list [--json]\n dench secrets delete NAME [--expected-version N] [--json]\n\nValues are accepted only through stdin or a file. Values are never printed.\nBind a stored name in dench-env.json secretEnv or secretFiles; env run injects\nonly those bindings. Memory is not credential storage. --kind defaults to env.\nExample: dench secrets set OPENAI_API_KEY --stdin < /private/path/key.txt\n```\n\nSee [ENVIRONMENTS.md](ENVIRONMENTS.md) for the manifest, preparation lifecycle, state and security rules.\n\n## Supported command index\n\nThis is an operation index, not a promise that a command has no required arguments. Local-only entries identify their execution boundary.\n\n| Command | Purpose |\n| --- | --- |\n| `dench env list` | List workspace-shared reusable setups and ready revisions (metadata only). |\n| `dench env show` | Inspect a saved setup, its manifest, and an optional immutable revision. |\n| `dench env save` | Publish an explicitly saved source and manifest for one-time preparation. Returns pending/building until verified; does not run the task. |\n| `dench env delete` | Retire a saved setup from new selection; retain immutable revisions and active job evidence. |\n| `dench env run` | Run a command with prepared dependencies and declared credentials, saving declared state with a revision check. |\n| `dench secrets list` | List workspace-shared credential names, kinds and versions; never values. |\n| `dench secrets set` | Encrypt a workspace credential. Use CLI stdin or the secure chat form; return metadata only. Do not put values in chat, manifests or scripts. |\n| `dench secrets delete` | Revoke a workspace credential for future resolution, optionally checking its version. |\n| `dench campaign draft` | Create an idempotent empty campaign draft with the caller's available sender pools. Does not launch or send. |\n| `dench campaign search` | Search people by filters without adding CRM records or campaign recipients. Search credits apply; this is not a free provider preview. |\n| `dench campaign add --search` | Search and add up to 50 people to an existing campaign. New identities become Leads. Search credits apply; use nextPageToken for another page. |\n| `dench campaign add --people-file` | Add up to 50 selected recipients without another search. Deduplicates, applies suppression, and preserves the campaign's launch state. |\n| `dench campaign sources get` | Read a campaign's saved signal source, targeting and schedule. |\n| `dench campaign sources signals` | Read signal choices, suggested terms and current source configuration. |\n| `dench campaign sources set` | Update source cadence, pause/resume, targeting, signals or terms. Active sources may perform billable future work. |\n| `dench campaign sources preview` | Preview a signal source using its current audience. Provider credits may apply; does not enroll people. |\n| `dench campaign builder profile` | Read the company profile and suggested campaign audience. |\n| `dench campaign builder save` | Save a signal campaign's audience, signals and terms idempotently. Does not start a build. |\n| `dench campaign builder get` | Read signal build progress, preview people, messages and errors. |\n| `dench campaign builder build` | Start the same billable signal/audience/message build used by the UI; poll the draft for progress. |\n| `dench campaign builder create` | Create a campaign from a reviewed signal draft. Does not launch the campaign. |\n| `dench campaign builder opener` | Edit or reset one preview person's opening line before creating the campaign. |\n| `dench campaign builder autofill` | Turn an audience description into targeting filters. AI credits apply. |\n| `dench campaign builder terms` | Suggest signal terms for the selected audience. AI credits apply. |\n| `dench campaign builder estimate` | Estimate audience or weekly signal volume using the UI's cached estimator. Provider credits may apply. |\n| `dench help` | List all CLI/API operations and parity classifications. |\n| `dench signin` | Create an agent sign-in request. |\n| `dench signin --email` | Start the email OTP sign-in flow. |\n| `dench sessions` | List local CLI sessions. |\n| `dench use <session-key-or-workspace-slug>` | Select a local CLI session. |\n| `dench logout` | Remove local CLI sessions. |\n| `dench backend show\\|set\\|clear` | Configure the CLI backend target. |\n| `dench context --sections` | Read selected scoped workspace documents and a compact context index without writes. |\n| `dench context` | Return the current Dench workspace context for the bearer. |\n| `dench context --instructions` | Return the current workspace's standing instructions for external agents. |\n| `dench status` | Return workspace and agent status. |\n| `dench agents` | List agents visible in the workspace status payload. |\n| `dench approvals` | List approval requests visible in the workspace status payload. |\n| `dench artifacts` | List artifacts for the authenticated workspace. |\n| `dench suggested-work` | List draft suggested-work artifacts. |\n| `dench lists` | List saved searches (find-result lists) in the workspace, newest first. |\n| `dench lists rows` | Read saved rows with current enrichment results and original indices; cursor-paged, without repeating a search. |\n| `dench artifacts import` | Explicitly import selected saved profiles, optionally filtered by enrichment, with a configured Lead status. Reuses CRM records; never sends outreach or triggers conversion. |\n| `dench lists show` | Get one saved search / list by artifact id. |\n| `dench lists save` | Save find results as a named list (rows + filters become a workspace artifact). |\n| `dench lists update` | Update a saved list in place (title, filters, and/or replacement rows). |\n| `dench lists rename` | Rename a saved search / list. |\n| `dench lists delete` | Delete a saved search / list, its exported CSV/JSONL files, and any linked watch. |\n| `dench lists reviews` | Read durable enrichment activity (legacy review metadata remains readable). Use lists rows for current saved profiles and contacts. |\n| `dench lists assess` | Suggest ICP fit for one profile. Never changes a human review decision. |\n| `dench memory search` | Search durable workspace memory. |\n| `dench memory save` | Save a durable workspace memory fact. |\n| `dench approval request` | Request human approval. |\n| `dench approval approve\\|reject` | Record an approval decision. |\n| `dench billing status` | Get billing and AI credit status. |\n| `dench billing topup` | Create an AI credit top-up checkout link. |\n| `dench upgrade` | Create a workspace upgrade checkout link. |\n| `dench chat list` | List past chat threads. |\n| `dench chat search` | Search past chat messages. |\n| `dench chat read` | Read a past chat thread. |\n| `dench chat tree` | Read the parent-child chat thread hierarchy. |\n| `dench chat rename` | Rename one or more chat threads. |\n| `dench chat delete` | Delete one or more chat threads. |\n| `dench chat new` | Start a new chat-turn workflow. |\n| `dench chat send` | Send a message to an existing chat thread. |\n| `dench chat follow` | Follow a live chat response stream. |\n| `dench crm objects list` | List CRM objects. |\n| `dench crm objects list --compact` | List names, display labels, visibility and workspace counts without internal state. |\n| `dench crm objects get` | Get a CRM object. |\n| `dench crm objects create` | Create a CRM object. |\n| `dench crm objects update` | Update a CRM object. |\n| `dench crm objects rename` | Rename a CRM object. |\n| `dench crm objects delete` | Delete a CRM object. |\n| `dench crm objects archive` | Hide a CRM object's workspace sidebar row for the whole workspace. Not a delete — the table keeps working. Admin-only. |\n| `dench crm objects unarchive` | Restore a CRM object's workspace sidebar row. Admin-only. |\n| `dench crm views list` | List the saved views on a CRM object (plus the active view). |\n| `dench crm views get` | Get one saved view's definition by name. |\n| `dench crm views entries` | Materialize a saved view's rows (applies its filters/sort/pins), paginated. |\n| `dench crm views create` | Create a saved view (named filters/sort/columns combo) on a CRM object. |\n| `dench crm views update` | Replace a saved view's definition (upsert by name). |\n| `dench crm views rename` | Rename a saved view in place — keeps its filters, pins, and sidebar slot. |\n| `dench crm views delete` | Delete a saved view by name (idempotent). |\n| `dench crm views archive` | Move a pinned view's sidebar row into the Archived flyout. Admin-only. |\n| `dench crm views unarchive` | Restore a view's sidebar row (re-asserts its pin). Admin-only. |\n| `dench crm sidebar list` | Describe the workspace sidebar: Library rows in tree order (with their groups), the home-level Pinned strip, what is hidden and why, and whether the caller is an admin. |\n| `dench crm sidebar move` | Reposition one Library row (or group) among the Library's top-level slots. Does not add a home-level Pinned tab. Workspace-shared; admin-only. |\n| `dench crm sidebar group create` | Create a named sidebar group (e.g. \"Hiring\") in the workspace-shared Library. Open to any member. |\n| `dench crm sidebar group rename` | Rename a sidebar group. Open to any member. |\n| `dench crm sidebar group delete` | Dissolve a sidebar group; its rows return to the top level in its slot. Open to any member. |\n| `dench crm sidebar group add` | Move a sidebar row into a group (groupId set) or back to the top level (groupId null). Open to any member. |\n| `dench crm sidebar pin` | Pin (pinned true) or unpin a Library row — or a whole group — on the home-level Pinned shortcut strip (the \"tab on the left\"). Does not move the row inside Library. Workspace-shared; open to any member. |\n| `dench crm fields list` | List fields for a CRM object, including read-only calculation metadata. |\n| `dench crm fields list --compact` | List field names, types and effective constraints without indexing or enrichment internals. |\n| `dench crm fields create` | Create a CRM field or read-only campaign activity boolean column. |\n| `dench crm fields update` | Update a CRM field or change/explicitly clear an existing campaign calculation. |\n| `dench crm fields delete` | Delete a CRM field. |\n| `dench crm fields reorder` | Reorder CRM fields. |\n| `dench crm fields enum-reorder` | Reorder enum values. |\n| `dench crm fields enum-add` | Add an enum value. |\n| `dench crm fields enum-remove` | Remove an enum value. |\n| `dench crm fields enum-rename` | Rename an enum value. |\n| `dench crm fields enum-set-color` | Set an enum color. |\n| `dench crm entries list` | List CRM entries. |\n| `dench crm entries get` | Get a CRM entry. |\n| `dench crm entries sync-history` | Queue a full, paged pull of the record's email history from every connected mailbox; the ledger fills as pages land. |\n| `dench crm entries sync-history --status` | Has Dench read this record's email history? One row per address per mailbox. |\n| `dench crm entries dossier` | Status of the account's research dossier — the delimited `dench:dossier` section Dench keeps in the record's Notes: status, when it was written, how many dated updates followed, the last error. |\n| `dench crm entries dossier --regenerate` | Research the account again and append a fresh dossier section to its Notes (needs a Website / Domain on the record). |\n| `dench crm privacy status` | Whether the caller hides their own emails / messages with this record from the workspace (directly, or through its account), and whether they own a connection the toggle applies to. |\n| `dench crm privacy hide\\|show` | Hide (`hidden: true`) or show the caller's own emails / messages with this record to the workspace. An account covers every contact and opportunity linked to it; existing ledger rows are restamped and the shared Brief regenerates. |\n| `dench crm detections list` | List account-detection verdicts — why a domain (or personal address) did or did not become an account: verdict, counterparty type, probabilities, evidence threads, the rows created. `kind` picks the creator: `detections` (email + sweep), `linkedin` (the LinkedIn resolver's rows) or `all` (default). Newest first. |\n| `dench crm detections undo` | Undo what the ingestion jobs created since a timestamp (admin): `kind` = `detections` (accounts + contacts from the email detector and the sweep), `linkedin` (the LinkedIn resolver's contacts + accounts; their conversations are unlinked and parked), `email-tasks` (auto-created tasks and cases) or `all` (default). Deletes only rows no human has touched and marks the verdicts / decisions rolled_back so they are never re-created. `dryRun` lists what would go; per-kind counts in `kinds`. |\n| `dench crm history` | List a CRM entry's audit history. |\n| `dench crm activity` | List a CRM entry's activity ledger (comments, notes, field changes, emails, meetings, Slack, LinkedIn) in one timeline, newest first by default. |\n| `dench crm brief` | The record's generated Brief: health, who owes the next move, open threads, commitments, risks, next step. Read this before the ledger. |\n| `dench crm context` | Read-only source diagnostics: accessible evidence, association reasons, last Brief source manifest, generation queue, and your mailbox delivery health. Bounded, cursor-paged snapshot; does not certify complete history. |\n| `dench crm brief --regenerate` | Rebuild a record's Brief from the current timeline. |\n| `dench crm notes list` | List a CRM entry's notes (titled, document-style comments), newest first. |\n| `dench crm notes create` | Add a titled note to a CRM entry's activity timeline (markdown body). Research goes here, never into the Notes document. |\n| `dench crm comments list` | List comments on a CRM entry (roots newest first, with preview replies). |\n| `dench crm comments replies` | List every reply under a root comment, oldest first. |\n| `dench crm comments get` | Get one comment with its entry label. |\n| `dench crm comments create` | Comment on a CRM entry (markdown). Mention members with [@Name](/?member=<userId>); mention Dench with [@Dench](/?member=dench) or mentionDench. |\n| `dench crm comments update` | Edit a comment you wrote. |\n| `dench crm comments delete` | Delete a comment (kept as a tombstone when it has replies). |\n| `dench crm comments react` | Toggle an emoji reaction on a comment. |\n| `dench crm watch status` | Whether you follow a CRM entry's activity. |\n| `dench crm watch` | Follow or unfollow a CRM entry's activity. |\n| `dench crm tasks mine --page` | Page assigned tasks in priority order (or completed tasks by recency). |\n| `dench crm tasks list --page` | Page all tasks linked to a record, including subtasks. |\n| `dench crm tasks prepare` | Start or resume preparation of exact task ordering and progress. |\n| `dench crm tasks mine` | Your open tasks (plus ones completed today). |\n| `dench crm tasks list` | Tasks linked to a CRM entry. |\n| `dench crm tasks create` | Create a task linked to a CRM entry. |\n| `dench crm tasks complete` | Mark a task done. |\n| `dench crm tasks reopen` | Reopen a completed task. |\n| `dench crm tasks priority` | Set or clear a task's priority. |\n| `dench crm tasks subtasks` | A task's subtasks. |\n| `dench crm tasks create-subtask` | Create a subtask under a task (inherits the parent's record relations). |\n| `dench crm tasks parent` | Nest a task under a parent, or promote it (parentEntryId: null). |\n| `dench crm tasks delete` | Delete a task and its subtasks, or keep them as tasks. |\n| `dench crm tasks repeat` | How a task repeats (rule, RRULE, trigger, ends, next due). |\n| `dench crm tasks repeat --off` | Stop a task repeating (past occurrences are kept). |\n| `dench crm tasks repeat --skip` | Skip the current occurrence: the next one is created now. |\n| `dench crm tasks repeat --pause` | Pause a repeating task. |\n| `dench crm tasks repeat --resume` | Resume a paused repeating task. |\n| `dench crm tasks series` | Every repeating-task series in the workspace. |\n| `dench crm tasks series <seriesId> --occurrences` | Every occurrence (task) of one series. |\n| `dench crm entries create` | Create a CRM entry. |\n| `dench crm entries create-many` | Create many CRM entries. |\n| `dench crm entries update` | Update a CRM entry. |\n| `dench crm entries update-many` | Update many CRM entries. |\n| `dench crm entries delete` | Delete a CRM entry. |\n| `dench crm entries bulk-delete` | Bulk delete CRM entries. |\n| `dench crm cells get` | Get a CRM cell value. |\n| `dench crm cells set` | Set a CRM cell value. |\n| `dench crm cells set-many` | Set many CRM cell values. |\n| `dench crm cells append` | Append to a CRM cell. |\n| `dench crm query` | Query CRM entries. |\n| `dench crm query --page` | Scan CRM entries with a resumable cursor. Empty filtered pages can have more results. |\n| `dench crm sql` | Run CRM SQL-compatible query. |\n| `dench crm aggregate` | Aggregate CRM entries. |\n| `dench crm search` | Search CRM entries. |\n| `dench crm search --page` | Search bounded pages of readable CRM entries, optionally projecting cells. Always follow continueCursor until isDone. |\n| `dench crm statuses list` | List CRM statuses. |\n| `dench crm statuses set` | Set CRM statuses. |\n| `dench crm batch` | Apply a CRM batch. |\n| `dench crm transaction begin` | Begin a CRM transaction. |\n| `dench crm transaction add` | Add an operation to a CRM transaction. |\n| `dench crm transaction commit` | Commit a CRM transaction. |\n| `dench crm transaction abort` | Abort a CRM transaction. |\n| `dench crm transaction inspect` | Inspect a CRM transaction. |\n| `dench crm docs list` | List CRM documents. |\n| `dench crm docs create` | Create a CRM document. |\n| `dench crm docs link` | Link a CRM document. |\n| `dench crm actions list` | List CRM actions. |\n| `dench crm actions create` | Create or upsert an action button on a named action column (fieldName is the column header; a column can hold multiple buttons — reuse fieldName to add more). |\n| `dench crm actions edit` | Update an action button on a CRM field. |\n| `dench crm actions delete` | Delete an action button from a CRM field. |\n| `dench crm actions run` | Run a CRM action on one or more entries. |\n| `dench members list` | List workspace members for CRM user fields. |\n| `dench members presence` | Live presence (online / away / offline) for workspace members. |\n| `dench members get` | A member's profile: name, email, tagline, role, timezone, presence, open task count. |\n| `dench members activity` | A member's recent comments and record changes across the CRM, newest first. |\n| `dench members tasks` | Open tasks assigned to a member (plus ones completed today). |\n| `dench members me` | Your own member profile. |\n| `dench members me --name … --tagline … --timezone …` | Update your name, tagline, avatar URL, or timezone. |\n| `dench members me --avatar <file>` | Get a one-time URL to POST an avatar image to; then PUT /me/avatar with the returned storageId. |\n| `dench members me --clear-avatar` | Remove your avatar. |\n| `dench crm reports generate` | Generate a CRM report. |\n| `dench crm analytics get` | Read an object's analytics dashboard: panels, field roles, the strip's collapsed flag, the named view's own dashboard (if any), and the fields panels may bucket on (synthetic $createdAt / $location last). |\n| `dench crm analytics compute` | Compute chart-ready results (buckets / series / values) for the stored dashboard, a subset of its panels, or ad-hoc panel definitions — optionally scoped to a saved view, a period preset (with the previous interval as previousValue) and extra filters. Same query the strip renders from. |\n| `dench crm analytics save` | Create or update one panel (upsert by id). With viewName the write lands on that saved view's own dashboard, forking a copy if it was still inheriting the object's. |\n| `dench crm analytics update` | Edit part of one stored panel: only the keys in `patch` change (title, type, metric, metricField, groupBy, dateField, dateBucket, filters, derived, span, hidden, sortOrder); null clears an optional key. What the card's ⋮ menu writes. |\n| `dench crm analytics hide` | Take a chart off the strip above the table (hidden: true). It stays in the full analytics view, counted by the strip's “+N more”. |\n| `dench crm analytics show` | Bring a hidden chart back onto the strip (hidden: false). |\n| `dench crm analytics resize` | Set a card's width on the 6-track dashboard grid: span 2 (third), 3 (half), 4 (two thirds), 6 (full), or null for the chart kind's default. |\n| `dench crm analytics delete` | Delete one panel (idempotent). Template panels are tombstoned so a re-install never resurrects them. |\n| `dench crm analytics reorder` | Persist the full panel order. panelIds must name every stored panel of the targeted dashboard. |\n| `dench crm analytics roles` | Set the object's analytics setup — the field roles shared by every chart and view (amount, stage, won/lost/converted values, stageProbabilities, closeDate, recurrence, owner, location). Replaces the mapping; with merge: true only the keys given change and null clears one. |\n| `dench crm analytics collapse` | Collapse the analytics strip above the object's table (team-shared, like saved views). |\n| `dench crm analytics expand` | Expand the analytics strip above the object's table. |\n| `dench crm analytics scope` | Give a saved view its own dashboard (mode \"own\": starts as a copy of the object's panels) or hand it back to the object's shared one (mode \"shared\"). |\n| `dench crm enrich cell` | Request enrichment for one CRM cell. |\n| `dench crm enrich object` | Request enrichment for a CRM object. |\n| `dench crm people search` | Search for people through the enrichment gateway. `companyDomains` takes up to 1000 domains (e.g. a resolved company list; `dench leads find --company-list <id\\|view>` resolves one for you) — the gateway fans large lists out as sequential vendor-safe chunks behind one `nextPageToken` stream. |\n| `dench crm companies search` | Search for companies through the enrichment gateway (also `dench companies find`). |\n| `dench jobs find` | Search job postings through Dench find. `companyDomains` takes up to 1000 domains (chunked automatically); `dench jobs find --company-list <id\\|view>` resolves a saved company list into it. |\n| `dench crm enrich profiles` | Look up up to 100 people or company profiles without buying emails or phones. Pass kind (people\\|companies) and lookups; results preserve inputIndex. People require linkedinUrl or email; companies require domain or linkedinUrl. |\n| `dench companies related` | Read company employees, founders, funding, investors, acquisitions, or investments. Pass domain or linkedinUrl, kind, limit (1–100), and optional pageToken. Does not import CRM records or buy contacts. |\n| `dench leads find --limit 1` | Preview how many records a find would return. |\n| `dench leads filters` | List Lead Finder filter vocabularies: exact enum values (seniorities, functions, industries, headcount, company types, languages) and curated suggestions (titles, locations, company HQ, universities, skills, keywords). |\n| `dench crm templates list` | List one-click CRM templates (objects + automations + sample rows) with per-workspace installed flags. |\n| `dench crm templates install` | Install a CRM template. Idempotent: re-installs top up missing pieces and never duplicate objects, automations, or sample rows. |\n| `dench crm templates remove-sample-data` | Delete a CRM template's sample rows (tagged \"Sample\"); schema, views, and automations stay. |\n| `dench crm automations list` | List deterministic CRM automations (rules that fire inside entry writes, e.g. qualified prospect → opportunity). |\n| `dench crm automations enable\\|disable` | Enable or disable a CRM automation. |\n| `dench crm import` | Import a local CSV into a CRM object. |\n| `dench crm export` | Export a CRM object to a local CSV. |\n| `dench meetings list` | List meetings, newest first, with status and participants. |\n| `dench meetings transcript get` | Read a meeting's transcript, in order, one page at a time. |\n| `dench meetings transcript set` | Append up to 500 transcript segments to a meeting (speakers with an email link to existing people). |\n| `dench meetings transcript clear` | Delete a batch of a meeting's transcript segments; repeat until `done` before re-appending. |\n| `dench meetings action-items list` | List a meeting's action items. |\n| `dench meetings action-items set` | Add action items to a meeting; `replace: true` swaps the whole list. |\n| `dench meetings action-items done` | Tick or untick one action item. |\n| `dench meetings action-items delete` | Delete one action item. |\n| `dench meetings participants list` | Who was in the meeting, merged with the linked calendar invite. |\n| `dench meetings participants set` | Record attendees by name/email; known members and existing people are linked, nobody is created. |\n| `dench meetings share` | Whether a meeting is shared with the whole workspace, and whether you may change that. |\n| `dench meetings share --workspace` | Share one meeting with the workspace, or take it back to owner + invitees. |\n| `dench meetings import` | Import calls from a local CSV/JSONL: meeting fields plus transcript, action items and attendees per row. |\n| `dench email identity verify` | Start Dench Emailing Service sender verification. |\n| `dench email identity status --identity-id` | Refresh a Dench Emailing Service sender verification status. |\n| `dench email identity status` | Read Dench Emailing Service sender verification status. |\n| `dench email identity list` | List the workspace's email sending identities. |\n| `dench email identity dns` | Fetch DNS records (DKIM/SPF/DMARC) for a pending domain sender verification. |\n| `dench email identity disable` | Soft-disable a sender (or every sender on a domain) for the workspace; sends are blocked until restored. |\n| `dench email identity restore` | Restore a previously disabled sender for the workspace. |\n| `dench email identity delete` | Permanently delete a sender (or every sender on a domain) from the workspace; re-adding it requires verification from scratch. |\n| `dench email inbox search` | Search authorized synced mailbox headers with bounded cursor pagination. isDone covers stored records, not provider completeness. |\n| `dench email inbox thread` | Read a bounded page of synced headers using an exact thread handle from inbox search; bodies remain on demand. |\n| `dench email inbox message` | Read a bounded stored body using an exact message handle from inbox search; rechecks mailbox and record privacy. |\n| `dench email draft create` | Create an editable Dench email draft and synchronize its selected native mailbox asynchronously. Use a stable requestId for retries. |\n| `dench email draft show` | Read an email draft, eligible senders and provider sync status. |\n| `dench email draft senders` | List native mailboxes this caller may use. |\n| `dench email draft update` | Save draft content using expectedRevision; sender changes require a new draft after provider binding. |\n| `dench email draft send` | Send the current Dench draft revision through its native mailbox. Repeated requests share a durable receipt. |\n| `dench email draft attach` | Upload attachment bytes authorized against this draft before adding its storage reference. |\n| `dench email draft signature` | Load the selected mailbox signature for explicit visible insertion. |\n| `dench email send [--mailbox <id/email>]` | Send through a native Gmail or Outlook mailbox with a durable request receipt. Only use for an explicit send request. |\n| `dench email send --from <verified-email>` | Send one raw email (no campaign): to/cc/bcc, custom from-name and reply-to, optional reply threading via replyToMessageId. |\n| `dench email message status` | Read a raw email message's status and open/click counters. |\n| `dench email message list` | List recent raw email messages for the workspace. |\n| `dench email template create` | Create a managed email campaign template. |\n| `dench email template preview` | Preview a managed email campaign template. |\n| `dench email campaign create` | Create a managed email campaign draft from a connected inbox or verified sender. |\n| `dench email campaign status` | Read campaign status and counters. |\n| `dench email campaign recipients add` | Add recipients to a managed email campaign. |\n| `dench email campaign preview` | Preview rendered messages for a campaign sample. |\n| `dench email campaign submit` | Submit a campaign for approval or sending. |\n| `dench email campaign pause` | Pause a sending email campaign. |\n| `dench email campaign resume` | Resume a paused email campaign. |\n| `dench email campaign cancel` | Cancel an email campaign. |\n| `dench email campaign list` | List managed email campaigns (paginated, optional status filter). |\n| `dench email campaign recipients list` | List a campaign's recipients (paginated, optional status filter). |\n| `dench email campaign recipients add-from-object` | Add campaign recipients from a CRM object: pick the object, the email field, and the entry ids. |\n| `dench email sequence create` | Create a follow-up sequence from a connected inbox or verified sender. |\n| `dench email sequence update` | Update a sequence's name, sender, or reply/stop settings. |\n| `dench email sequence activate` | Activate a sequence so its enrollments begin sending. |\n| `dench email sequence deactivate` | Deactivate a sequence (pause sending, revert to draft). |\n| `dench email sequence archive` | Archive a sequence (stops all sending). |\n| `dench email sequence list` | List the workspace's follow-up sequences. |\n| `dench email sequence status` | Read a sequence with its ordered steps. |\n| `dench email sequence steps add` | Add a step (subject, body, and wait delay) to a sequence. |\n| `dench email sequence steps update` | Update a sequence step's subject, body, or delay. |\n| `dench email sequence steps remove` | Remove a step from a sequence. |\n| `dench email sequence steps reorder` | Reorder a sequence's steps. |\n| `dench email sequence enroll` | Enroll explicit recipients into a sequence. |\n| `dench email sequence enroll-from-object` | Enroll people from a CRM object: pick the object, the email field, and the entry ids. |\n| `dench email sequence enrollments list` | List a sequence's enrollments (paginated, optional status filter). |\n| `dench email sequence enrollment pause` | Pause one enrollment. |\n| `dench email sequence enrollment resume` | Resume one paused enrollment. |\n| `dench email sequence enrollment stop` | Stop one enrollment (no further steps send). |\n| `dench campaign accounts` | Discover LinkedIn sender ids, labels, availability and remaining capacity visible to the acting workspace member. Never returns provider credentials. |\n| `dench campaign list` | List outreach campaigns (archived ones on request). |\n| `dench campaign create` | Create a draft outreach campaign with email and LinkedIn sender pools and a business-hours send window in the given timezone. |\n| `dench campaign duplicate` | Copy a campaign into a new draft named \"<name> copy\": the same steps, schedule, sender pools and personalized lines, its lead source paused, and no people. To reach more than 300 people, duplicate and add the next page of the search to the copy. |\n| `dench campaign get` | Read a campaign: settings, steps, people, their current step and hold, last touches, linked leads. |\n| `dench campaign pipeline` | Where a campaign's people sit in the workspace's lead pipeline right now: each lead Status in order with how many enrolled leads are at it, and how many have no lead or a status outside the pipeline. |\n| `dench campaign set` | Change a campaign's name, sender pools, send windows, daily email cap, stop-on-reply, start or end. Works until archived; existing conversations keep their assigned sender. |\n| `dench campaign steps set` | Replace every step of a draft campaign (email, wait, LinkedIn visit, connection request, message, post reaction, InMail, If / else, Wait for them to accept). A workflow (stepModel workflow, or steps with key, next, yesNext or noNext) branches: condition and wait_for_connection steps continue on yesNext or noNext, other steps on next; each points at another step's key or end. Steps are checked first: a field the step type would drop, missing text, {TOKEN} single braces or paths that don't hold together are rejected, not saved. Configure sender pools on the campaign; step sender ids are legacy-only. |\n| `dench campaign steps edit` | Edit the text, waits and conditions of a launched (or paused) campaign's steps in place. Each step keeps its id, type and place; pass the stepId and the updatedAt you read, and only the fields you change. A workflow keeps its paths and what each If / else checks (key, next, yesNext, noNext, check, conditionField, conditionOperator); conditionValue and acceptance waits stay editable. Messages already sent or being prepared keep their text; drafts use campaigns.steps.replace. |\n| `dench campaign personalize get` | Read a campaign's personalized lines (written for each person): their {{keys}} for steps, instructions and fallbacks, how many people have them, and the cost per person. |\n| `dench campaign personalize set` | Define up to 5 personalized lines (each used as {{key}} in steps) and what you sell. Saving starts writing missing lines for up to 100 people, about 3 cents a person; for more it returns needsConfirm and writes nothing until campaigns.personalization.write is confirmed. |\n| `dench campaign personalize clear` | Turn off a campaign's personalized lines and cancel the ones not yet written. Refused while a step still uses one of the {{keys}}. |\n| `dench campaign personalize write` | Write personalized lines for the people missing them (mode missing) or rewrite everyone's (mode all). Rewriting everyone or more than 100 people returns needsConfirm and the estimate until called with confirmed: true. |\n| `dench campaign personalize edit` | Set one person's personalized line by hand; an empty value undoes the edit. Braces are removed and text past the line's length is cut. |\n| `dench campaign personalize rewrite` | Write a fresh personalized line for one person: one key, or all of their lines when key is left out. About 3 cents. |\n| `dench campaign add` | Add CRM records (up to 100 ids per call) to a campaign; returns how many were added and why others were skipped. |\n| `dench campaign add --name` | Add one person by email and/or LinkedIn profile URL. |\n| `dench campaign preview` | Show the message one person gets at one email or LinkedIn step, with their fields and personalized lines filled in: subject, body, sender, recipient, merge fields still missing, warnings, and why the step would be skipped. Nothing is sent, prepared or charged. |\n| `dench campaign launch --check` | Launch readiness without launching: blockers grouped by problem, warnings, and the fingerprint `launch` requires. |\n| `dench campaign launch` | Launch a ready draft. Pass the fingerprint from the preview and acknowledge the LinkedIn notice when LinkedIn steps exist. |\n| `dench campaign pause` | Pause an active campaign. |\n| `dench campaign resume` | Resume a paused campaign. |\n| `dench campaign archive` | Archive a campaign and stop everyone still in flight. This is the app's Delete: campaigns are never erased, and their people, leads and activity stay. |\n| `dench campaign events` | The campaign's activity log, newest first (sent, accepted, replied, skipped, failed, paused, stopped). |\n| `dench campaign stop` | Stop one person: nothing more is sent to them from this campaign. |\n| `dench campaign resume-person` | Resume one paused person in an active campaign. |\n| `dench campaign person set --email` | Give a person in a draft campaign a new or corrected email address; their linked CRM record gets it too. Only before launch; another person in the campaign can't share it. |\n| `dench campaign person set --linkedin-url` | Give a person in a draft campaign a new or corrected LinkedIn profile URL; their linked CRM record gets it too. Only before launch. |\n| `dench campaign person set --field` | Fill or fix one {{merge field}} for a person in a draft campaign, e.g. a missing Company that blocks launch; a matching column on their CRM record is updated too. Only before launch; written personalized lines are not merge fields and can't be set here. |\n| `dench campaign resume-person --all` | Resume every paused person in an active campaign at once, e.g. after a sending account is reconnected. Returns how many were resumed. |\n| `dench campaign sync` | Fill each unfinished person's empty email, LinkedIn URL, name and merge fields from their linked CRM record, e.g. after enriching the records. Never overwrites a value the campaign already has; returns how many people changed. |\n| `dench campaign where` | The campaigns a CRM record (a person or a lead) is in, newest first, with the person's status in each and the accounts that send to them. |\n| `dench campaign person show` | Everything a campaign did for one person, step by step: what was sent (subject and text), whether it was opened or clicked, what was skipped and why, waits, failures and retries. |\n| `dench campaign remove` | Remove a person who was never contacted from a draft campaign. Frees their place in the 300-person cap and keeps the draft launchable; stop anyone already contacted instead. |\n| `dench cron list` | List routines. |\n| `dench cron history` | List routine run history. |\n| `dench cron get` | Get a routine. |\n| `dench cron create` | Create a routine. |\n| `dench cron update` | Update a routine. |\n| `dench cron delete` | Delete a routine. |\n| `dench cron enable` | Enable a routine. |\n| `dench cron disable` | Disable a routine. |\n| `dench cron run` | Run a routine now. |\n| `dench cron runs` | List runs for a routine. |\n| `dench files ls` | List workspace files. |\n| `dench files cat` | Read a workspace file's text. |\n| `dench files write` | Write a workspace file's text. |\n| `dench files download` | Get a signed file download URL. |\n| `dench files rm` | Remove a workspace file or directory. |\n| `dench files mv` | Move a workspace file or directory. |\n| `dench files mkdir` | Create a workspace directory (and any missing parents). Idempotent. |\n| `dench stage` | Stage uploaded content into the workspace. |\n| `dench fs` | Run the local file sync daemon. |\n| `dench search` | Search the web through the Dench gateway. |\n| `dench search contents` | Fetch page contents through the Dench gateway. |\n| `dench search answer` | Answer a question with web search. |\n| `dench web scrape` | Read a page as clean Markdown through Dench web. |\n| `dench web brand` | Look up a brand kit from a domain or email. |\n| `dench web sitemap` | List sitemap URLs for a domain. |\n| `dench web crawl` | Crawl a site to Markdown (capped). |\n| `dench web screenshot` | Capture a rendered page screenshot. |\n| `dench web extract` | Extract typed fields from a page using a JSON Schema. |\n| `dench web product` | Extract a product listing from a URL. |\n| `dench web competitors` | Find competitors of a domain. |\n| `dench web styleguide` | Read a brand styleguide from a domain. |\n| `dench web fonts` | Read site fonts from a domain. |\n| `dench web classify` | Classify a domain with NAICS or SIC. |\n| `dench web brand-search` | Autocomplete brand names. |\n| `dench image generate` | Generate an image through the Dench gateway. |\n| `dench image edit` | Edit an image through the Dench gateway. |\n| `dench integrations` | List every connected integration — connected apps plus the Gmail, Outlook, Calendar, LinkedIn and Slack connections Dench provides itself (Slack rows report source: dench). |\n| `dench apps list` | List installed App Store apps in the workspace. |\n| `dench apps gallery` | List Discover listings — Official (Dench) + Community shelves. |\n| `dench apps create` | Scaffold and register a new App Store app. |\n| `dench apps install` | Install an App Store listing into the workspace. |\n| `dench apps publish` | Publish an app's files as a versioned App Store listing. |\n| `dench apps uninstall` | Uninstall an app from the workspace. |\n| `dench apps enable` | Enable or disable an installed app. |\n| `dench apps pin` | Pin or unpin an installed app in the rail. |\n| `dench apps validate` | Validate a .dench.app folder: manifest, permissions, and every window.dench call it makes. |\n| `dench apps webhooks register` | Register an inbound webhook for an app. Returns the URL and signing secret ONCE. |\n| `dench apps webhooks list` | List an app's registered webhooks (URLs, never secrets). |\n| `dench apps webhooks poll` | Read events an app webhook received since a timestamp. |\n| `dench apps webhooks delete` | Remove an app webhook and every event it stored. |\n| `dench tool status` | List connected tool accounts. |\n| `dench tool connect` | Create a tool OAuth connection link. Slack is in-house, never a third-party connection: for toolkit slack the CLI prints the first-party install URL (/api/slack/install?slug=<workspace>, org admin) instead of calling this endpoint. |\n| `dench tool search` | Search for external tool actions. |\n| `dench tool run` | Run an external tool action. SLACK_* slugs (SLACK_SEND_MESSAGE with Block Kit blocks/buttons, SLACK_FETCH_HISTORY, SLACK_LIST_CHANNELS, …) run through Dench's in-house Slack app install — any channel in the connected workspace — and are always preferred over any third-party Slack connection. |\n| `dench tool disconnect` | Disconnect an external tool account. |\n| `dench tool labels` | List human names given to connected accounts. |\n| `dench tool label` | Name a connected account, or clear its name with an empty string. |\n| `dench browser` | Drive Dench Browser with a single interactive action (open, inspect, click, fill, type, screenshot, stop). Pass the step in `action` plus its arguments. |\n| `dench browser session create` | Create a Dench Browser session and return a CDP connect URL for Playwright (chromium.connectOverCDP) plus a live-view URL. Use for durable scripts run from the sandbox. |\n| `dench browser session status` | Get the status, connect URL, and live-view URL for a Dench Browser session. |\n| `dench browser session stop` | Stop a Dench Browser session and release the remote browser. |\n| `dench context --sections identity` | Read IDENTITY.md. |\n| `dench context set identity` | Update IDENTITY.md. |\n| `dench context --sections organisation` | Read ORGANISATION.md. |\n| `dench context set organisation` | Update ORGANISATION.md. |\n| `dench context --sections user` | Read USER.md. |\n| `dench context set user` | Update USER.md. |\n| `dench context --sections tools` | Read tool notes. |\n| `dench context set tools` | Update tool notes. |\n| `dench context set rules` | Update standing workspace rules; credentials belong in secrets and runnable setup in environments. |\n| `dench model default` | Set the default chat model. |\n| `dench model thread` | Set a thread model override. |\n| `dench notifications list` | List your notifications (inbox, unread, archived, or all). |\n| `dench notifications count` | How many unread notifications you have. |\n| `dench notifications read` | Mark a notification read (or unread). |\n| `dench notifications read-all` | Mark every inbox notification read. |\n| `dench notifications archive` | Archive a notification. |\n| `dench notifications prefs` | Read your notification preferences. |\n| `dench notifications prefs --set` | Update your notification preferences. |\n| `dench settings sync` | Read the workspace's Sync switches (accounts from email, contacts from LinkedIn, Slack Connect, calendar, dossier, tasks from email, lead auto-qualify, summaries and Brief). All on by default. |\n| `dench settings sync set` | Flip Sync switches (admin). Partial patch: only the keys sent change. Off stops new work; nothing already created is removed. |\n\n## Compatibility\n\nExisting identity/organisation/user/tools aliases still address the same stored documents. Prefer `dench context --sections …` and `dench context set …`. Aggregate `dench mem` and bootstrap endpoints remain compatibility readers/writers while existing clients migrate; they are not the recommended memory API. Existing history is retained. No `dench agent spawn/await/message/wait-message/pause/resume/continue/tree` commands exist; use the harness subagent tools or supported `dench chat` commands.\n"
|
|
34
|
+
"sha256": "3b27bb5deb74bf1377b04d53f16d678f4e97843102ddaecd48ca61f7ea044fdc",
|
|
35
|
+
"content": "<img src=\"/logos/apple-touch-icon.png\" alt=\"Dench\" width=\"32\" height=\"32\">\n\n# Dench CLI\n\nGenerated from the command registry, native file-command contracts and saved-setup parsers. Run `dench <command> --help` for arguments and flags. API-only upload phases and compatibility aliases are not standalone commands.\n\nUse `dench context --sections organisation,user,memory` for knowledge, `dench memory` for facts, `dench env` for explicitly saved executable setups, and `dench secrets` for encrypted credentials. `dench cron secret` signs inbound webhooks; it does not store service credentials.\n\n## Reusable setups\n\n```text\ndench env — verified setups reused across chats\n\n dench env list [--limit N] [--cursor CURSOR] [--json]\n dench env show NAME [--revision SHA256] [--json]\n dench env save NAME --path DIRECTORY [--wait SECONDS | --no-wait] [--json]\n dench env run NAME [--revision SHA256] [--state-key KEY] [--prepare-local] [--json] -- COMMAND [ARGS...]\n dench env delete NAME [--json]\n\nSave requires dench-env.json, SKILL.md, an explicit files allowlist and verify\ncommands. Credentials, caches and symlinks are excluded from source uploads.\nSave returns an authoritative preparation receipt; default observation is 10s.\nReady runs reuse prepared dependencies. --prepare-local explicitly prepares a\nseparate local platform cache once; ordinary ready runs never reinstall.\nState is job-local by default; choose an explicit state key to share it.\nExample: dench env run openai-report -- node report.mjs\n```\n\n```text\ndench secrets — workspace-shared encrypted credentials\n\n dench secrets set NAME --stdin [--kind env|file] [--expected-version N] [--json]\n dench secrets set NAME --file PATH [--kind env|file] [--expected-version N] [--json]\n dench secrets list [--json]\n dench secrets delete NAME [--expected-version N] [--json]\n\nValues are accepted only through stdin or a file. Values are never printed.\nBind a stored name in dench-env.json secretEnv or secretFiles; env run injects\nonly those bindings. Memory is not credential storage. --kind defaults to env.\nExample: dench secrets set OPENAI_API_KEY --stdin < /private/path/key.txt\n```\n\nSee [ENVIRONMENTS.md](ENVIRONMENTS.md) for the manifest, preparation lifecycle, state and security rules.\n\n## Workspace files\n\nWorkspace paths are POSIX paths under /workspace; /reports/a.md and /workspace/reports/a.md address the same file.\n\nThese commands read or update workspace storage. Use dench stage to upload local files or directories.\n\n### files ls\n\nList workspace files; the default path is /.\n\n```text\ndench files ls [<path>] [--recursive|-r] [--json]\n```\n\n--recursive (or -r) includes descendants.\n\n```sh\ndench files ls /reports --recursive --json\n```\n\n### files cat\n\nRead a workspace file's inline text.\n\n```text\ndench files cat <path> [--json]\n```\n\nUse cat for virtual files such as IDENTITY.md and ORGANISATION.md, which have no download blob.\n\nFor binary files or files without inline text, use files download.\n\n```sh\ndench files cat /reports/summary.md\n```\n\n### files write\n\nWrite a workspace file's text from stdin.\n\n```text\ndench files write <path> [--json]\n```\n\nThe destination path is required. Pipe or redirect the raw UTF-8 file text into stdin.\n\n--json controls the result format; stdin is file content, not an API JSON envelope. No --stdin, --content or --file flag is supported.\n\nWriting replaces the complete text; empty stdin writes an empty file.\n\n```sh\ndench files write /reports/summary.md --json < ./summary.md\nprintf '%s\\n' 'Report ready.' | dench files write /reports/summary.md --json\n```\n\n### files mv\n\nMove or rename a workspace file or directory.\n\n```text\ndench files mv <src> <dst> [--json]\n```\n\nBoth paths refer to workspace storage, not local disk.\n\n```sh\ndench files mv /reports/draft.md /reports/summary.md --json\n```\n\n### files rm\n\nRemove a workspace file or directory.\n\n```text\ndench files rm <path> [--recursive|-r] [--json]\n```\n\nDirectories require --recursive (or -r). An absent path is already removed.\n\n```sh\ndench files rm /reports/old --recursive --json\n```\n\n### files download\n\nDownload workspace bytes to local disk, or print a signed URL.\n\n```text\ndench files download <path> [<local-dest>] [--url] [--json]\n```\n\nWithout --url, downloads directories recursively and defaults the local destination to the path's basename (/ becomes workspace).\n\n--url prints a short-lived signed URL for one stored file instead of saving locally; local-dest is unused in this mode.\n\n```sh\ndench files download /reports/summary.pdf ./summary.pdf --json\ndench files download /reports/summary.pdf --url --json\n```\n\n\n## Supported command index\n\nThis is an operation index, not a promise that a command has no required arguments. Local-only entries identify their execution boundary.\n\n| Command | Purpose |\n| --- | --- |\n| `dench env list` | List workspace-shared reusable setups and ready revisions (metadata only). |\n| `dench env show` | Inspect a saved setup, its manifest, and an optional immutable revision. |\n| `dench env save` | Publish an explicitly saved source and manifest for one-time preparation. Returns pending/building until verified; does not run the task. |\n| `dench env delete` | Retire a saved setup from new selection; retain immutable revisions and active job evidence. |\n| `dench env run` | Run a command with prepared dependencies and declared credentials, saving declared state with a revision check. |\n| `dench secrets list` | List workspace-shared credential names, kinds and versions; never values. |\n| `dench secrets set` | Encrypt a workspace credential. Use CLI stdin or the secure chat form; return metadata only. Do not put values in chat, manifests or scripts. |\n| `dench secrets delete` | Revoke a workspace credential for future resolution, optionally checking its version. |\n| `dench campaign draft` | Create an idempotent empty campaign draft with the caller's available sender pools. Does not launch or send. |\n| `dench campaign search` | Search people by filters without adding CRM records or campaign recipients. Search credits apply; this is not a free provider preview. |\n| `dench campaign add --search` | Search and add up to 50 people to an existing campaign. New identities become Leads. Search credits apply; use nextPageToken for another page. |\n| `dench campaign add --people-file` | Add up to 50 selected recipients without another search. Deduplicates, applies suppression, and preserves the campaign's launch state. |\n| `dench campaign sources get` | Read a campaign's saved signal source, targeting and schedule. |\n| `dench campaign sources signals` | Read signal choices, suggested terms and current source configuration. |\n| `dench campaign sources set` | Update source cadence, pause/resume, targeting, signals or terms. Active sources may perform billable future work. |\n| `dench campaign sources preview` | Preview a signal source using its current audience. Provider credits may apply; does not enroll people. |\n| `dench campaign builder profile` | Read the company profile and suggested campaign audience. |\n| `dench campaign builder save` | Save a signal campaign's audience, signals and terms idempotently. Does not start a build. |\n| `dench campaign builder get` | Read signal build progress, preview people, messages and errors. |\n| `dench campaign builder build` | Start the same billable signal/audience/message build used by the UI; poll the draft for progress. |\n| `dench campaign builder create` | Create a campaign from a reviewed signal draft. Does not launch the campaign. |\n| `dench campaign builder opener` | Edit or reset one preview person's opening line before creating the campaign. |\n| `dench campaign builder autofill` | Turn an audience description into targeting filters. AI credits apply. |\n| `dench campaign builder terms` | Suggest signal terms for the selected audience. AI credits apply. |\n| `dench campaign builder estimate` | Estimate audience or weekly signal volume using the UI's cached estimator. Provider credits may apply. |\n| `dench help` | List all CLI/API operations and parity classifications. |\n| `dench signin` | Create an agent sign-in request. |\n| `dench signin --email` | Start the email OTP sign-in flow. |\n| `dench sessions` | List local CLI sessions. |\n| `dench use <session-key-or-workspace-slug>` | Select a local CLI session. |\n| `dench logout` | Remove local CLI sessions. |\n| `dench backend show\\|set\\|clear` | Configure the CLI backend target. |\n| `dench context --sections` | Read selected scoped workspace documents and a compact context index without writes. |\n| `dench context` | Return the current Dench workspace context for the bearer. |\n| `dench context --instructions` | Return the current workspace's standing instructions for external agents. |\n| `dench status` | Return workspace and agent status. |\n| `dench agents` | List agents visible in the workspace status payload. |\n| `dench approvals` | List approval requests visible in the workspace status payload. |\n| `dench artifacts` | List artifacts for the authenticated workspace. |\n| `dench suggested-work` | List draft suggested-work artifacts. |\n| `dench lists` | List saved searches (find-result lists) in the workspace, newest first. |\n| `dench lists rows` | Read saved rows with current enrichment results and original indices; cursor-paged, without repeating a search. |\n| `dench artifacts import` | Explicitly import selected saved profiles, optionally filtered by enrichment, with a configured Lead status. Reuses CRM records; never sends outreach or triggers conversion. |\n| `dench lists show` | Get one saved search / list by artifact id. |\n| `dench lists save` | Save find results as a named list (rows + filters become a workspace artifact). |\n| `dench lists update` | Update a saved list in place (title, filters, and/or replacement rows). |\n| `dench lists rename` | Rename a saved search / list. |\n| `dench lists delete` | Delete a saved search / list, its exported CSV/JSONL files, and any linked watch. |\n| `dench lists reviews` | Read durable enrichment activity (legacy review metadata remains readable). Use lists rows for current saved profiles and contacts. |\n| `dench lists assess` | Suggest ICP fit for one profile. Never changes a human review decision. |\n| `dench memory search` | Search durable workspace memory. |\n| `dench memory save` | Save a durable workspace memory fact. |\n| `dench approval request` | Request human approval. |\n| `dench approval approve\\|reject` | Record an approval decision. |\n| `dench billing status` | Get billing and AI credit status. |\n| `dench billing topup` | Create an AI credit top-up checkout link. |\n| `dench upgrade` | Create a workspace upgrade checkout link. |\n| `dench chat list` | List past chat threads. |\n| `dench chat search` | Search past chat messages. |\n| `dench chat read` | Read a past chat thread. |\n| `dench chat tree` | Read the parent-child chat thread hierarchy. |\n| `dench chat rename` | Rename one or more chat threads. |\n| `dench chat delete` | Delete one or more chat threads. |\n| `dench chat new` | Start a new chat-turn workflow. |\n| `dench chat send` | Send a message to an existing chat thread. |\n| `dench chat follow` | Follow a live chat response stream. |\n| `dench crm objects list` | List CRM objects. |\n| `dench crm objects list --compact` | List names, display labels, visibility and workspace counts without internal state. |\n| `dench crm objects get` | Get a CRM object. |\n| `dench crm objects create` | Create a CRM object. |\n| `dench crm objects update` | Update a CRM object. |\n| `dench crm objects rename` | Rename a CRM object. |\n| `dench crm objects delete` | Delete a CRM object. |\n| `dench crm objects archive` | Hide a CRM object's workspace sidebar row for the whole workspace. Not a delete — the table keeps working. Admin-only. |\n| `dench crm objects unarchive` | Restore a CRM object's workspace sidebar row. Admin-only. |\n| `dench crm views list` | List the saved views on a CRM object (plus the active view). |\n| `dench crm views get` | Get one saved view's definition by name. |\n| `dench crm views entries` | Materialize a saved view's rows (applies its filters/sort/pins), paginated. |\n| `dench crm views create` | Create a saved view (named filters/sort/columns combo) on a CRM object. |\n| `dench crm views update` | Replace a saved view's definition (upsert by name). |\n| `dench crm views rename` | Rename a saved view in place — keeps its filters, pins, and sidebar slot. |\n| `dench crm views delete` | Delete a saved view by name (idempotent). |\n| `dench crm views archive` | Move a pinned view's sidebar row into the Archived flyout. Admin-only. |\n| `dench crm views unarchive` | Restore a view's sidebar row (re-asserts its pin). Admin-only. |\n| `dench crm sidebar list` | Describe the workspace sidebar: Library rows in tree order (with their groups), the home-level Pinned strip, what is hidden and why, and whether the caller is an admin. |\n| `dench crm sidebar move` | Reposition one Library row (or group) among the Library's top-level slots. Does not add a home-level Pinned tab. Workspace-shared; admin-only. |\n| `dench crm sidebar group create` | Create a named sidebar group (e.g. \"Hiring\") in the workspace-shared Library. Open to any member. |\n| `dench crm sidebar group rename` | Rename a sidebar group. Open to any member. |\n| `dench crm sidebar group delete` | Dissolve a sidebar group; its rows return to the top level in its slot. Open to any member. |\n| `dench crm sidebar group add` | Move a sidebar row into a group (groupId set) or back to the top level (groupId null). Open to any member. |\n| `dench crm sidebar pin` | Pin (pinned true) or unpin a Library row — or a whole group — on the home-level Pinned shortcut strip (the \"tab on the left\"). Does not move the row inside Library. Workspace-shared; open to any member. |\n| `dench crm fields list` | List fields for a CRM object, including read-only calculation metadata. |\n| `dench crm fields list --compact` | List field names, types and effective constraints without indexing or enrichment internals. |\n| `dench crm fields create` | Create a CRM field or read-only campaign activity boolean column. |\n| `dench crm fields update` | Update a CRM field or change/explicitly clear an existing campaign calculation. |\n| `dench crm fields delete` | Delete a CRM field. |\n| `dench crm fields reorder` | Reorder CRM fields. |\n| `dench crm fields enum-reorder` | Reorder enum values. |\n| `dench crm fields enum-add` | Add an enum value. |\n| `dench crm fields enum-remove` | Remove an enum value. |\n| `dench crm fields enum-rename` | Rename an enum value. |\n| `dench crm fields enum-set-color` | Set an enum color. |\n| `dench crm entries list` | List CRM entries. |\n| `dench crm entries get` | Get a CRM entry. |\n| `dench crm entries sync-history` | Queue a full, paged pull of the record's email history from every connected mailbox; the ledger fills as pages land. |\n| `dench crm entries sync-history --status` | Has Dench read this record's email history? One row per address per mailbox. |\n| `dench crm entries dossier` | Status of the account's research dossier — the delimited `dench:dossier` section Dench keeps in the record's Notes: status, when it was written, how many dated updates followed, the last error. |\n| `dench crm entries dossier --regenerate` | Research the account again and append a fresh dossier section to its Notes (needs a Website / Domain on the record). |\n| `dench crm privacy status` | Whether the caller hides their own emails / messages with this record from the workspace (directly, or through its account), and whether they own a connection the toggle applies to. |\n| `dench crm privacy hide\\|show` | Hide (`hidden: true`) or show the caller's own emails / messages with this record to the workspace. An account covers every contact and opportunity linked to it; existing ledger rows are restamped and the shared Brief regenerates. |\n| `dench crm detections list` | List account-detection verdicts — why a domain (or personal address) did or did not become an account: verdict, counterparty type, probabilities, evidence threads, the rows created. `kind` picks the creator: `detections` (email + sweep), `linkedin` (the LinkedIn resolver's rows) or `all` (default). Newest first. |\n| `dench crm detections undo` | Undo what the ingestion jobs created since a timestamp (admin): `kind` = `detections` (accounts + contacts from the email detector and the sweep), `linkedin` (the LinkedIn resolver's contacts + accounts; their conversations are unlinked and parked), `email-tasks` (auto-created tasks and cases) or `all` (default). Deletes only rows no human has touched and marks the verdicts / decisions rolled_back so they are never re-created. `dryRun` lists what would go; per-kind counts in `kinds`. |\n| `dench crm history` | List a CRM entry's audit history. |\n| `dench crm activity` | List a CRM entry's activity ledger (comments, notes, field changes, emails, meetings, Slack, LinkedIn) in one timeline, newest first by default. |\n| `dench crm brief` | The record's generated Brief: health, who owes the next move, open threads, commitments, risks, next step. Read this before the ledger. |\n| `dench crm context` | Read-only source diagnostics: accessible evidence, association reasons, last Brief source manifest, generation queue, and your mailbox delivery health. Bounded, cursor-paged snapshot; does not certify complete history. |\n| `dench crm brief --regenerate` | Rebuild a record's Brief from the current timeline. |\n| `dench crm notes list` | List a CRM entry's notes (titled, document-style comments), newest first. |\n| `dench crm notes create` | Add a titled note to a CRM entry's activity timeline (markdown body). Research goes here, never into the Notes document. |\n| `dench crm comments list` | List comments on a CRM entry (roots newest first, with preview replies). |\n| `dench crm comments replies` | List every reply under a root comment, oldest first. |\n| `dench crm comments get` | Get one comment with its entry label. |\n| `dench crm comments create` | Comment on a CRM entry (markdown). Mention members with [@Name](/?member=<userId>); mention Dench with [@Dench](/?member=dench) or mentionDench. |\n| `dench crm comments update` | Edit a comment you wrote. |\n| `dench crm comments delete` | Delete a comment (kept as a tombstone when it has replies). |\n| `dench crm comments react` | Toggle an emoji reaction on a comment. |\n| `dench crm watch status` | Whether you follow a CRM entry's activity. |\n| `dench crm watch` | Follow or unfollow a CRM entry's activity. |\n| `dench crm tasks mine --page` | Page assigned tasks in priority order (or completed tasks by recency). |\n| `dench crm tasks list --page` | Page all tasks linked to a record, including subtasks. |\n| `dench crm tasks prepare` | Start or resume preparation of exact task ordering and progress. |\n| `dench crm tasks mine` | Your open tasks (plus ones completed today). |\n| `dench crm tasks list` | Tasks linked to a CRM entry. |\n| `dench crm tasks create` | Create a task linked to a CRM entry. |\n| `dench crm tasks complete` | Mark a task done. |\n| `dench crm tasks reopen` | Reopen a completed task. |\n| `dench crm tasks priority` | Set or clear a task's priority. |\n| `dench crm tasks subtasks` | A task's subtasks. |\n| `dench crm tasks create-subtask` | Create a subtask under a task (inherits the parent's record relations). |\n| `dench crm tasks parent` | Nest a task under a parent, or promote it (parentEntryId: null). |\n| `dench crm tasks delete` | Delete a task and its subtasks, or keep them as tasks. |\n| `dench crm tasks repeat` | How a task repeats (rule, RRULE, trigger, ends, next due). |\n| `dench crm tasks repeat --off` | Stop a task repeating (past occurrences are kept). |\n| `dench crm tasks repeat --skip` | Skip the current occurrence: the next one is created now. |\n| `dench crm tasks repeat --pause` | Pause a repeating task. |\n| `dench crm tasks repeat --resume` | Resume a paused repeating task. |\n| `dench crm tasks series` | Every repeating-task series in the workspace. |\n| `dench crm tasks series <seriesId> --occurrences` | Every occurrence (task) of one series. |\n| `dench crm entries create` | Create a CRM entry. |\n| `dench crm entries create-many` | Create many CRM entries. |\n| `dench crm entries update` | Update a CRM entry. |\n| `dench crm entries update-many` | Update many CRM entries. |\n| `dench crm entries delete` | Delete a CRM entry. |\n| `dench crm entries bulk-delete` | Bulk delete CRM entries. |\n| `dench crm cells get` | Get a CRM cell value. |\n| `dench crm cells set` | Set a CRM cell value. |\n| `dench crm cells set-many` | Set many CRM cell values. |\n| `dench crm cells append` | Append to a CRM cell. |\n| `dench crm query` | Query CRM entries. |\n| `dench crm query --page` | Scan CRM entries with a resumable cursor. Empty filtered pages can have more results. |\n| `dench crm sql` | Run CRM SQL-compatible query. |\n| `dench crm aggregate` | Aggregate CRM entries. |\n| `dench crm search` | Search CRM entries. |\n| `dench crm search --page` | Search bounded pages of readable CRM entries, optionally projecting cells. Always follow continueCursor until isDone. |\n| `dench crm statuses list` | List CRM statuses. |\n| `dench crm statuses set` | Set CRM statuses. |\n| `dench crm batch` | Apply a CRM batch. |\n| `dench crm transaction begin` | Begin a CRM transaction. |\n| `dench crm transaction add` | Add an operation to a CRM transaction. |\n| `dench crm transaction commit` | Commit a CRM transaction. |\n| `dench crm transaction abort` | Abort a CRM transaction. |\n| `dench crm transaction inspect` | Inspect a CRM transaction. |\n| `dench crm docs list` | List CRM documents. |\n| `dench crm docs create` | Create a CRM document. |\n| `dench crm docs link` | Link a CRM document. |\n| `dench crm actions list` | List CRM actions. |\n| `dench crm actions create` | Create or upsert an action button on a named action column (fieldName is the column header; a column can hold multiple buttons — reuse fieldName to add more). |\n| `dench crm actions edit` | Update an action button on a CRM field. |\n| `dench crm actions delete` | Delete an action button from a CRM field. |\n| `dench crm actions run` | Run a CRM action on one or more entries. |\n| `dench members list` | List workspace members for CRM user fields. |\n| `dench members presence` | Live presence (online / away / offline) for workspace members. |\n| `dench members get` | A member's profile: name, email, tagline, role, timezone, presence, open task count. |\n| `dench members activity` | A member's recent comments and record changes across the CRM, newest first. |\n| `dench members tasks` | Open tasks assigned to a member (plus ones completed today). |\n| `dench members me` | Your own member profile. |\n| `dench members me --name … --tagline … --timezone …` | Update your name, tagline, avatar URL, or timezone. |\n| `dench members me --avatar <file>` | Get a one-time URL to POST an avatar image to; then PUT /me/avatar with the returned storageId. |\n| `dench members me --clear-avatar` | Remove your avatar. |\n| `dench crm reports generate` | Generate a CRM report. |\n| `dench crm analytics get` | Read an object's analytics dashboard: panels, field roles, the strip's collapsed flag, the named view's own dashboard (if any), and the fields panels may bucket on (synthetic $createdAt / $location last). |\n| `dench crm analytics compute` | Compute chart-ready results (buckets / series / values) for the stored dashboard, a subset of its panels, or ad-hoc panel definitions — optionally scoped to a saved view, a period preset (with the previous interval as previousValue) and extra filters. Same query the strip renders from. |\n| `dench crm analytics save` | Create or update one panel (upsert by id). With viewName the write lands on that saved view's own dashboard, forking a copy if it was still inheriting the object's. |\n| `dench crm analytics update` | Edit part of one stored panel: only the keys in `patch` change (title, type, metric, metricField, groupBy, dateField, dateBucket, filters, derived, span, hidden, sortOrder); null clears an optional key. What the card's ⋮ menu writes. |\n| `dench crm analytics hide` | Take a chart off the strip above the table (hidden: true). It stays in the full analytics view, counted by the strip's “+N more”. |\n| `dench crm analytics show` | Bring a hidden chart back onto the strip (hidden: false). |\n| `dench crm analytics resize` | Set a card's width on the 6-track dashboard grid: span 2 (third), 3 (half), 4 (two thirds), 6 (full), or null for the chart kind's default. |\n| `dench crm analytics delete` | Delete one panel (idempotent). Template panels are tombstoned so a re-install never resurrects them. |\n| `dench crm analytics reorder` | Persist the full panel order. panelIds must name every stored panel of the targeted dashboard. |\n| `dench crm analytics roles` | Set the object's analytics setup — the field roles shared by every chart and view (amount, stage, won/lost/converted values, stageProbabilities, closeDate, recurrence, owner, location). Replaces the mapping; with merge: true only the keys given change and null clears one. |\n| `dench crm analytics collapse` | Collapse the analytics strip above the object's table (team-shared, like saved views). |\n| `dench crm analytics expand` | Expand the analytics strip above the object's table. |\n| `dench crm analytics scope` | Give a saved view its own dashboard (mode \"own\": starts as a copy of the object's panels) or hand it back to the object's shared one (mode \"shared\"). |\n| `dench crm enrich cell` | Request enrichment for one CRM cell. |\n| `dench crm enrich object` | Request enrichment for a CRM object. |\n| `dench crm people search` | Search for people through the enrichment gateway. `companyDomains` takes up to 1000 domains (e.g. a resolved company list; `dench leads find --company-list <id\\|view>` resolves one for you) — the gateway fans large lists out as sequential vendor-safe chunks behind one `nextPageToken` stream. |\n| `dench crm companies search` | Search for companies through the enrichment gateway (also `dench companies find`). |\n| `dench jobs find` | Search job postings through Dench find. `companyDomains` takes up to 1000 domains (chunked automatically); `dench jobs find --company-list <id\\|view>` resolves a saved company list into it. |\n| `dench crm enrich profiles` | Look up up to 100 people or company profiles without buying emails or phones. Pass kind (people\\|companies) and lookups; results preserve inputIndex. People require linkedinUrl or email; companies require domain or linkedinUrl. |\n| `dench companies related` | Read company employees, founders, funding, investors, acquisitions, or investments. Pass domain or linkedinUrl, kind, limit (1–100), and optional pageToken. Does not import CRM records or buy contacts. |\n| `dench leads find --limit 1` | Preview how many records a find would return. |\n| `dench leads filters` | List Lead Finder filter vocabularies: exact enum values (seniorities, functions, industries, headcount, company types, languages) and curated suggestions (titles, locations, company HQ, universities, skills, keywords). |\n| `dench crm templates list` | List one-click CRM templates (objects + automations + sample rows) with per-workspace installed flags. |\n| `dench crm templates install` | Install a CRM template. Idempotent: re-installs top up missing pieces and never duplicate objects, automations, or sample rows. |\n| `dench crm templates remove-sample-data` | Delete a CRM template's sample rows (tagged \"Sample\"); schema, views, and automations stay. |\n| `dench crm automations list` | List deterministic CRM automations (rules that fire inside entry writes, e.g. qualified prospect → opportunity). |\n| `dench crm automations enable\\|disable` | Enable or disable a CRM automation. |\n| `dench crm import` | Import a local CSV into a CRM object. |\n| `dench crm export` | Export a CRM object to a local CSV. |\n| `dench meetings list` | List meetings, newest first, with status and participants. |\n| `dench meetings transcript get` | Read a meeting's transcript, in order, one page at a time. |\n| `dench meetings transcript set` | Append up to 500 transcript segments to a meeting (speakers with an email link to existing people). |\n| `dench meetings transcript clear` | Delete a batch of a meeting's transcript segments; repeat until `done` before re-appending. |\n| `dench meetings action-items list` | List a meeting's action items. |\n| `dench meetings action-items set` | Add action items to a meeting; `replace: true` swaps the whole list. |\n| `dench meetings action-items done` | Tick or untick one action item. |\n| `dench meetings action-items delete` | Delete one action item. |\n| `dench meetings participants list` | Who was in the meeting, merged with the linked calendar invite. |\n| `dench meetings participants set` | Record attendees by name/email; known members and existing people are linked, nobody is created. |\n| `dench meetings share` | Whether a meeting is shared with the whole workspace, and whether you may change that. |\n| `dench meetings share --workspace` | Share one meeting with the workspace, or take it back to owner + invitees. |\n| `dench meetings import` | Import calls from a local CSV/JSONL: meeting fields plus transcript, action items and attendees per row. |\n| `dench email identity verify` | Start Dench Emailing Service sender verification. |\n| `dench email identity status --identity-id` | Refresh a Dench Emailing Service sender verification status. |\n| `dench email identity status` | Read Dench Emailing Service sender verification status. |\n| `dench email identity list` | List the workspace's email sending identities. |\n| `dench email identity dns` | Fetch DNS records (DKIM/SPF/DMARC) for a pending domain sender verification. |\n| `dench email identity disable` | Soft-disable a sender (or every sender on a domain) for the workspace; sends are blocked until restored. |\n| `dench email identity restore` | Restore a previously disabled sender for the workspace. |\n| `dench email identity delete` | Permanently delete a sender (or every sender on a domain) from the workspace; re-adding it requires verification from scratch. |\n| `dench email inbox search` | Search authorized synced mailbox headers with bounded cursor pagination. isDone covers stored records, not provider completeness. |\n| `dench email inbox thread` | Read a bounded page of synced headers using an exact thread handle from inbox search; bodies remain on demand. |\n| `dench email inbox message` | Read a bounded stored body using an exact message handle from inbox search; rechecks mailbox and record privacy. |\n| `dench email draft create` | Create an editable Dench email draft and synchronize its selected native mailbox asynchronously. Use a stable requestId for retries. |\n| `dench email draft show` | Read an email draft, eligible senders and provider sync status. |\n| `dench email draft senders` | List native mailboxes this caller may use. |\n| `dench email draft update` | Save draft content using expectedRevision; sender changes require a new draft after provider binding. |\n| `dench email draft send` | Send the current Dench draft revision through its native mailbox. Repeated requests share a durable receipt. |\n| `dench email draft attach` | Upload attachment bytes authorized against this draft before adding its storage reference. |\n| `dench email draft signature` | Load the selected mailbox signature for explicit visible insertion. |\n| `dench email send [--mailbox <id/email>]` | Send through a native Gmail or Outlook mailbox with a durable request receipt. Only use for an explicit send request. |\n| `dench email send --from <verified-email>` | Send one raw email (no campaign): to/cc/bcc, custom from-name and reply-to, optional reply threading via replyToMessageId. |\n| `dench email message status` | Read a raw email message's status and open/click counters. |\n| `dench email message list` | List recent raw email messages for the workspace. |\n| `dench email template create` | Create a managed email campaign template. |\n| `dench email template preview` | Preview a managed email campaign template. |\n| `dench email campaign create` | Create a managed email campaign draft from a connected inbox or verified sender. |\n| `dench email campaign status` | Read campaign status and counters. |\n| `dench email campaign recipients add` | Add recipients to a managed email campaign. |\n| `dench email campaign preview` | Preview rendered messages for a campaign sample. |\n| `dench email campaign submit` | Submit a campaign for approval or sending. |\n| `dench email campaign pause` | Pause a sending email campaign. |\n| `dench email campaign resume` | Resume a paused email campaign. |\n| `dench email campaign cancel` | Cancel an email campaign. |\n| `dench email campaign list` | List managed email campaigns (paginated, optional status filter). |\n| `dench email campaign recipients list` | List a campaign's recipients (paginated, optional status filter). |\n| `dench email campaign recipients add-from-object` | Add campaign recipients from a CRM object: pick the object, the email field, and the entry ids. |\n| `dench email sequence create` | Create a follow-up sequence from a connected inbox or verified sender. |\n| `dench email sequence update` | Update a sequence's name, sender, or reply/stop settings. |\n| `dench email sequence activate` | Activate a sequence so its enrollments begin sending. |\n| `dench email sequence deactivate` | Deactivate a sequence (pause sending, revert to draft). |\n| `dench email sequence archive` | Archive a sequence (stops all sending). |\n| `dench email sequence list` | List the workspace's follow-up sequences. |\n| `dench email sequence status` | Read a sequence with its ordered steps. |\n| `dench email sequence steps add` | Add a step (subject, body, and wait delay) to a sequence. |\n| `dench email sequence steps update` | Update a sequence step's subject, body, or delay. |\n| `dench email sequence steps remove` | Remove a step from a sequence. |\n| `dench email sequence steps reorder` | Reorder a sequence's steps. |\n| `dench email sequence enroll` | Enroll explicit recipients into a sequence. |\n| `dench email sequence enroll-from-object` | Enroll people from a CRM object: pick the object, the email field, and the entry ids. |\n| `dench email sequence enrollments list` | List a sequence's enrollments (paginated, optional status filter). |\n| `dench email sequence enrollment pause` | Pause one enrollment. |\n| `dench email sequence enrollment resume` | Resume one paused enrollment. |\n| `dench email sequence enrollment stop` | Stop one enrollment (no further steps send). |\n| `dench campaign accounts` | Discover LinkedIn sender ids, labels, availability and remaining capacity visible to the acting workspace member. Never returns provider credentials. |\n| `dench campaign list` | List outreach campaigns (archived ones on request). |\n| `dench campaign create` | Create a draft outreach campaign with email and LinkedIn sender pools and a business-hours send window in the given timezone. |\n| `dench campaign duplicate` | Copy a campaign into a new draft named \"<name> copy\": the same steps, schedule, sender pools and personalized lines, its lead source paused, and no people. To reach more than 300 people, duplicate and add the next page of the search to the copy. |\n| `dench campaign get` | Read a campaign: settings, steps, people, their current step and hold, last touches, linked leads. |\n| `dench campaign pipeline` | Where a campaign's people sit in the workspace's lead pipeline right now: each lead Status in order with how many enrolled leads are at it, and how many have no lead or a status outside the pipeline. |\n| `dench campaign set` | Change a campaign's name, sender pools, send windows, daily email cap, stop-on-reply, start or end. Works until archived; existing conversations keep their assigned sender. |\n| `dench campaign steps set` | Replace every step of a draft campaign (email, wait, LinkedIn visit, connection request, message, post reaction, InMail, If / else, Wait for them to accept). A workflow (stepModel workflow, or steps with key, next, yesNext or noNext) branches: condition and wait_for_connection steps continue on yesNext or noNext, other steps on next; each points at another step's key or end. Steps are checked first: a field the step type would drop, missing text, {TOKEN} single braces or paths that don't hold together are rejected, not saved. Configure sender pools on the campaign; step sender ids are legacy-only. |\n| `dench campaign steps edit` | Edit the text, waits and conditions of a launched (or paused) campaign's steps in place. Each step keeps its id, type and place; pass the stepId and the updatedAt you read, and only the fields you change. A workflow keeps its paths and what each If / else checks (key, next, yesNext, noNext, check, conditionField, conditionOperator); conditionValue and acceptance waits stay editable. Messages already sent or being prepared keep their text; drafts use campaigns.steps.replace. |\n| `dench campaign personalize get` | Read a campaign's personalized lines (written for each person): their {{keys}} for steps, instructions and fallbacks, how many people have them, and the cost per person. |\n| `dench campaign personalize set` | Define up to 5 personalized lines (each used as {{key}} in steps) and what you sell. Saving starts writing missing lines for up to 100 people, about 3 cents a person; for more it returns needsConfirm and writes nothing until campaigns.personalization.write is confirmed. |\n| `dench campaign personalize clear` | Turn off a campaign's personalized lines and cancel the ones not yet written. Refused while a step still uses one of the {{keys}}. |\n| `dench campaign personalize write` | Write personalized lines for the people missing them (mode missing) or rewrite everyone's (mode all). Rewriting everyone or more than 100 people returns needsConfirm and the estimate until called with confirmed: true. |\n| `dench campaign personalize edit` | Set one person's personalized line by hand; an empty value undoes the edit. Braces are removed and text past the line's length is cut. |\n| `dench campaign personalize rewrite` | Write a fresh personalized line for one person: one key, or all of their lines when key is left out. About 3 cents. |\n| `dench campaign add` | Add CRM records (up to 100 ids per call) to a campaign; returns how many were added and why others were skipped. |\n| `dench campaign add --name` | Add one person by email and/or LinkedIn profile URL. |\n| `dench campaign preview` | Show the message one person gets at one email or LinkedIn step, with their fields and personalized lines filled in: subject, body, sender, recipient, merge fields still missing, warnings, and why the step would be skipped. Nothing is sent, prepared or charged. |\n| `dench campaign launch --check` | Launch readiness without launching: blockers grouped by problem, warnings, and the fingerprint `launch` requires. |\n| `dench campaign launch` | Launch a ready draft. Pass the fingerprint from the preview and acknowledge the LinkedIn notice when LinkedIn steps exist. |\n| `dench campaign pause` | Pause an active campaign. |\n| `dench campaign resume` | Resume a paused campaign. |\n| `dench campaign archive` | Archive a campaign and stop everyone still in flight. This is the app's Delete: campaigns are never erased, and their people, leads and activity stay. |\n| `dench campaign events` | The campaign's activity log, newest first (sent, accepted, replied, skipped, failed, paused, stopped). |\n| `dench campaign stop` | Stop one person: nothing more is sent to them from this campaign. |\n| `dench campaign resume-person` | Resume one paused person in an active campaign. |\n| `dench campaign person set --email` | Give a person in a draft campaign a new or corrected email address; their linked CRM record gets it too. Only before launch; another person in the campaign can't share it. |\n| `dench campaign person set --linkedin-url` | Give a person in a draft campaign a new or corrected LinkedIn profile URL; their linked CRM record gets it too. Only before launch. |\n| `dench campaign person set --field` | Fill or fix one {{merge field}} for a person in a draft campaign, e.g. a missing Company that blocks launch; a matching column on their CRM record is updated too. Only before launch; written personalized lines are not merge fields and can't be set here. |\n| `dench campaign resume-person --all` | Resume every paused person in an active campaign at once, e.g. after a sending account is reconnected. Returns how many were resumed. |\n| `dench campaign sync` | Fill each unfinished person's empty email, LinkedIn URL, name and merge fields from their linked CRM record, e.g. after enriching the records. Never overwrites a value the campaign already has; returns how many people changed. |\n| `dench campaign where` | The campaigns a CRM record (a person or a lead) is in, newest first, with the person's status in each and the accounts that send to them. |\n| `dench campaign person show` | Everything a campaign did for one person, step by step: what was sent (subject and text), whether it was opened or clicked, what was skipped and why, waits, failures and retries. |\n| `dench campaign remove` | Remove a person who was never contacted from a draft campaign. Frees their place in the 300-person cap and keeps the draft launchable; stop anyone already contacted instead. |\n| `dench cron list` | List routines. |\n| `dench cron history` | List routine run history. |\n| `dench cron get` | Get a routine. |\n| `dench cron create` | Create a routine. |\n| `dench cron update` | Update a routine. |\n| `dench cron delete` | Delete a routine. |\n| `dench cron enable` | Enable a routine. |\n| `dench cron disable` | Disable a routine. |\n| `dench cron run` | Run a routine now. |\n| `dench cron runs` | List runs for a routine. |\n| `dench files ls [<path>] [--recursive\\|-r] [--json]` | List workspace files; the default path is /. |\n| `dench files cat <path> [--json]` | Read a workspace file's inline text. |\n| `dench files write <path> [--json]` | Write a workspace file's text from stdin. |\n| `dench files download <path> [<local-dest>] [--url] [--json]` | Download workspace bytes to local disk, or print a signed URL. |\n| `dench files rm <path> [--recursive\\|-r] [--json]` | Remove a workspace file or directory. |\n| `dench files mv <src> <dst> [--json]` | Move or rename a workspace file or directory. |\n| `dench stage` | Stage uploaded content into the workspace. |\n| `dench fs` | Run the local file sync daemon. |\n| `dench search` | Search the web through the Dench gateway. |\n| `dench search contents` | Fetch page contents through the Dench gateway. |\n| `dench search answer` | Answer a question with web search. |\n| `dench web scrape` | Read a page as clean Markdown through Dench web. |\n| `dench web brand` | Look up a brand kit from a domain or email. |\n| `dench web sitemap` | List sitemap URLs for a domain. |\n| `dench web crawl` | Crawl a site to Markdown (capped). |\n| `dench web screenshot` | Capture a rendered page screenshot. |\n| `dench web extract` | Extract typed fields from a page using a JSON Schema. |\n| `dench web product` | Extract a product listing from a URL. |\n| `dench web competitors` | Find competitors of a domain. |\n| `dench web styleguide` | Read a brand styleguide from a domain. |\n| `dench web fonts` | Read site fonts from a domain. |\n| `dench web classify` | Classify a domain with NAICS or SIC. |\n| `dench web brand-search` | Autocomplete brand names. |\n| `dench image generate` | Generate an image through the Dench gateway. |\n| `dench image edit` | Edit an image through the Dench gateway. |\n| `dench integrations` | List every connected integration — connected apps plus the Gmail, Outlook, Calendar, LinkedIn and Slack connections Dench provides itself (Slack rows report source: dench). |\n| `dench apps list` | List installed App Store apps in the workspace. |\n| `dench apps gallery` | List Discover listings — Official (Dench) + Community shelves. |\n| `dench apps create` | Scaffold and register a new App Store app. |\n| `dench apps install` | Install an App Store listing into the workspace. |\n| `dench apps publish` | Publish an app's files as a versioned App Store listing. |\n| `dench apps uninstall` | Uninstall an app from the workspace. |\n| `dench apps enable` | Enable or disable an installed app. |\n| `dench apps pin` | Pin or unpin an installed app in the rail. |\n| `dench apps validate` | Validate a .dench.app folder: manifest, permissions, and every window.dench call it makes. |\n| `dench apps webhooks register` | Register an inbound webhook for an app. Returns the URL and signing secret ONCE. |\n| `dench apps webhooks list` | List an app's registered webhooks (URLs, never secrets). |\n| `dench apps webhooks poll` | Read events an app webhook received since a timestamp. |\n| `dench apps webhooks delete` | Remove an app webhook and every event it stored. |\n| `dench tool status` | List connected tool accounts. |\n| `dench tool connect` | Create a tool OAuth connection link. Slack is in-house, never a third-party connection: for toolkit slack the CLI prints the first-party install URL (/api/slack/install?slug=<workspace>, org admin) instead of calling this endpoint. |\n| `dench tool search` | Search for external tool actions. |\n| `dench tool run` | Run an external tool action. SLACK_* slugs (SLACK_SEND_MESSAGE with Block Kit blocks/buttons, SLACK_FETCH_HISTORY, SLACK_LIST_CHANNELS, …) run through Dench's in-house Slack app install — any channel in the connected workspace — and are always preferred over any third-party Slack connection. |\n| `dench tool disconnect` | Disconnect an external tool account. |\n| `dench tool labels` | List human names given to connected accounts. |\n| `dench tool label` | Name a connected account, or clear its name with an empty string. |\n| `dench browser` | Drive Dench Browser with a single interactive action (open, inspect, click, fill, type, screenshot, stop). Pass the step in `action` plus its arguments. |\n| `dench browser session create` | Create a Dench Browser session and return a CDP connect URL for Playwright (chromium.connectOverCDP) plus a live-view URL. Use for durable scripts run from the sandbox. |\n| `dench browser session status` | Get the status, connect URL, and live-view URL for a Dench Browser session. |\n| `dench browser session stop` | Stop a Dench Browser session and release the remote browser. |\n| `dench context --sections identity` | Read IDENTITY.md. |\n| `dench context set identity` | Update IDENTITY.md. |\n| `dench context --sections organisation` | Read ORGANISATION.md. |\n| `dench context set organisation` | Update ORGANISATION.md. |\n| `dench context --sections user` | Read USER.md. |\n| `dench context set user` | Update USER.md. |\n| `dench context --sections tools` | Read tool notes. |\n| `dench context set tools` | Update tool notes. |\n| `dench context set rules` | Update standing workspace rules; credentials belong in secrets and runnable setup in environments. |\n| `dench model default` | Set the default chat model. |\n| `dench model thread` | Set a thread model override. |\n| `dench notifications list` | List your notifications (inbox, unread, archived, or all). |\n| `dench notifications count` | How many unread notifications you have. |\n| `dench notifications read` | Mark a notification read (or unread). |\n| `dench notifications read-all` | Mark every inbox notification read. |\n| `dench notifications archive` | Archive a notification. |\n| `dench notifications prefs` | Read your notification preferences. |\n| `dench notifications prefs --set` | Update your notification preferences. |\n| `dench settings sync` | Read the workspace's Sync switches (accounts from email, contacts from LinkedIn, Slack Connect, calendar, dossier, tasks from email, lead auto-qualify, summaries and Brief). All on by default. |\n| `dench settings sync set` | Flip Sync switches (admin). Partial patch: only the keys sent change. Off stops new work; nothing already created is removed. |\n\n## Compatibility\n\nExisting identity/organisation/user/tools aliases still address the same stored documents. Prefer `dench context --sections …` and `dench context set …`. Aggregate `dench mem` and bootstrap endpoints remain compatibility readers/writers while existing clients migrate; they are not the recommended memory API. Existing history is retained. No `dench agent spawn/await/message/wait-message/pause/resume/continue/tree` commands exist; use the harness subagent tools or supported `dench chat` commands.\n"
|
|
36
36
|
},
|
|
37
37
|
"references/CMO.md": {
|
|
38
38
|
"sha256": "2dbbdb126ae4c02bed59aa961e1392b2200b16ee9c135372fe33af87b79739a4",
|
package/meetings.ts
CHANGED
|
@@ -721,7 +721,11 @@ async function runImport(ctx: CrmCliContext): Promise<void> {
|
|
|
721
721
|
// ── help ───────────────────────────────────────────────────────────────────
|
|
722
722
|
|
|
723
723
|
function meetingsHelp(): void {
|
|
724
|
-
console.log(
|
|
724
|
+
console.log(meetingsHelpText());
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
export function meetingsHelpText(): string {
|
|
728
|
+
return `Usage: dench meetings <subcommand>
|
|
725
729
|
|
|
726
730
|
A meeting is an ordinary CRM record on the protected \`meeting\` object —
|
|
727
731
|
create, update and delete it with \`dench crm entries … meeting\`, query it
|
|
@@ -778,5 +782,5 @@ Import many calls at once:
|
|
|
778
782
|
created/updated meeting gets its transcript, items and attendees.
|
|
779
783
|
Run it as a signed-in member: meetings are owner-scoped records.
|
|
780
784
|
|
|
781
|
-
Read public/references/CRM-IMPORT.md ("Importing meetings") before a bulk import
|
|
785
|
+
Read public/references/CRM-IMPORT.md ("Importing meetings") before a bulk import.`;
|
|
782
786
|
}
|
package/members.ts
CHANGED
|
@@ -440,7 +440,11 @@ async function callQuery(
|
|
|
440
440
|
}
|
|
441
441
|
|
|
442
442
|
function membersHelp(): void {
|
|
443
|
-
console.log(
|
|
443
|
+
console.log(membersHelpText());
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export function membersHelpText(): string {
|
|
447
|
+
return `Usage: dench members <subcommand>
|
|
444
448
|
|
|
445
449
|
Workspace members for the active organization. Use these userIds when
|
|
446
450
|
assigning CRM \`user\`-type cells (e.g. \`Owner\`, \`Assignee\`) so the
|
|
@@ -483,5 +487,5 @@ The server best-effort-resolves a name / email passed to a \`user\`
|
|
|
483
487
|
field when the match against the roster is unique, but the explicit
|
|
484
488
|
lookup via \`dench members list\` is the correct pattern. Unknown field
|
|
485
489
|
names are rejected on write (pass --allow-unknown to bypass).
|
|
486
|
-
|
|
490
|
+
`;
|
|
487
491
|
}
|
package/package.json
CHANGED
package/search.ts
CHANGED
|
@@ -519,7 +519,11 @@ async function runAnswerSubcommand(ctx: SearchCliContext): Promise<void> {
|
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
function searchHelp(): void {
|
|
522
|
-
console.log(
|
|
522
|
+
console.log(searchHelpText());
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export function searchHelpText(): string {
|
|
526
|
+
return `Usage: dench search <subcommand>
|
|
523
527
|
|
|
524
528
|
Live web access through the Dench Cloud Gateway. Auths
|
|
525
529
|
with DENCH_API_KEY or the active \`dench signin\` agent session. Agent
|
|
@@ -566,7 +570,7 @@ Environment variables:
|
|
|
566
570
|
DENCH_HOST If it points at localhost, defaults to
|
|
567
571
|
http://localhost:8787 instead of production.
|
|
568
572
|
|
|
569
|
-
Exit codes: 0 success, non-zero on gateway / network errors
|
|
573
|
+
Exit codes: 0 success, non-zero on gateway / network errors.`;
|
|
570
574
|
}
|
|
571
575
|
|
|
572
576
|
// ────────────────────────────────────────────────────────────────────────────
|
package/tools.ts
CHANGED
|
@@ -1402,8 +1402,8 @@ function redactForDisplay(value: unknown, key?: string): unknown {
|
|
|
1402
1402
|
// Help text
|
|
1403
1403
|
// ---------------------------------------------------------------------------
|
|
1404
1404
|
|
|
1405
|
-
export function
|
|
1406
|
-
|
|
1405
|
+
export function toolHelpText(): string {
|
|
1406
|
+
return `Dench tool commands
|
|
1407
1407
|
|
|
1408
1408
|
Usage:
|
|
1409
1409
|
dench integrations [--json]
|
|
@@ -1436,10 +1436,14 @@ Notes:
|
|
|
1436
1436
|
LinkedIn tools are QUEUED, not run inline, so account-safety pacing (daily
|
|
1437
1437
|
caps, working hours, spacing) applies. They return an action_id — poll it with
|
|
1438
1438
|
LINKEDIN_GET_ACTION_STATUS.
|
|
1439
|
-
dench tool connect prints the OAuth redirect URL for the human to open.
|
|
1440
|
-
dench tool run output is redacted for display; pass --json for raw output.
|
|
1439
|
+
\`dench tool connect\` prints the OAuth redirect URL for the human to open.
|
|
1440
|
+
\`dench tool run\` output is redacted for display; pass --json for raw output.
|
|
1441
1441
|
Override the gateway base with DENCH_GATEWAY_URL or GATEWAY_URL.
|
|
1442
|
-
|
|
1442
|
+
`;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
export function toolHelp() {
|
|
1446
|
+
console.log(toolHelpText());
|
|
1443
1447
|
}
|
|
1444
1448
|
|
|
1445
1449
|
// ---------------------------------------------------------------------------
|
package/web.ts
CHANGED
|
@@ -20,7 +20,10 @@ class WebCliError extends Error {}
|
|
|
20
20
|
const CRAWL_DEFAULT_PAGES = 15;
|
|
21
21
|
const CRAWL_MAX_PAGES = 50;
|
|
22
22
|
|
|
23
|
-
function parsePositiveInt(
|
|
23
|
+
function parsePositiveInt(
|
|
24
|
+
name: string,
|
|
25
|
+
raw: string | undefined,
|
|
26
|
+
): number | undefined {
|
|
24
27
|
if (raw === undefined) return undefined;
|
|
25
28
|
const parsed = Number(raw);
|
|
26
29
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
@@ -125,7 +128,11 @@ function formatMarkdown(data: unknown): string {
|
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
export function webHelp(): void {
|
|
128
|
-
console.log(
|
|
131
|
+
console.log(webHelpText());
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function webHelpText(): string {
|
|
135
|
+
return `Dench web
|
|
129
136
|
|
|
130
137
|
Read pages, brand kits, sitemaps, screenshots, and structured extracts
|
|
131
138
|
through the Dench gateway. Prefer these over writing your own scraper.
|
|
@@ -145,7 +152,7 @@ through the Dench gateway. Prefer these over writing your own scraper.
|
|
|
145
152
|
|
|
146
153
|
Crawl defaults to 15 pages and is hard-capped at 50. Search stays on
|
|
147
154
|
\`dench search\`. \`dench context\` is workspace orientation, not this.
|
|
148
|
-
|
|
155
|
+
`;
|
|
149
156
|
}
|
|
150
157
|
|
|
151
158
|
async function runWebSubcommand(ctx: WebCliContext): Promise<void> {
|
|
@@ -199,7 +206,9 @@ async function runWebSubcommand(ctx: WebCliContext): Promise<void> {
|
|
|
199
206
|
assertNoUnknownFlags(rest, "web sitemap");
|
|
200
207
|
const domain = rest[0];
|
|
201
208
|
if (!domain) {
|
|
202
|
-
throw new WebCliError(
|
|
209
|
+
throw new WebCliError(
|
|
210
|
+
"Usage: dench web sitemap <domain> [--limit N] [--json]",
|
|
211
|
+
);
|
|
203
212
|
}
|
|
204
213
|
const data = await callWebOp(ctx, "web-sitemap", {
|
|
205
214
|
domain,
|
|
@@ -246,7 +255,11 @@ async function runWebSubcommand(ctx: WebCliContext): Promise<void> {
|
|
|
246
255
|
fullScreenshot: fullPage,
|
|
247
256
|
});
|
|
248
257
|
const record = asRecord(data);
|
|
249
|
-
out(
|
|
258
|
+
out(
|
|
259
|
+
ctx,
|
|
260
|
+
data,
|
|
261
|
+
asString(record?.screenshot) ?? JSON.stringify(data, null, 2),
|
|
262
|
+
);
|
|
250
263
|
return;
|
|
251
264
|
}
|
|
252
265
|
|
|
@@ -310,7 +323,8 @@ async function runWebSubcommand(ctx: WebCliContext): Promise<void> {
|
|
|
310
323
|
if (sub === "fonts") {
|
|
311
324
|
assertNoUnknownFlags(rest, "web fonts");
|
|
312
325
|
const domain = rest[0];
|
|
313
|
-
if (!domain)
|
|
326
|
+
if (!domain)
|
|
327
|
+
throw new WebCliError("Usage: dench web fonts <domain> [--json]");
|
|
314
328
|
const data = await callWebOp(ctx, "brand-fonts", { domain });
|
|
315
329
|
out(ctx, data, JSON.stringify(data, null, 2));
|
|
316
330
|
return;
|