@dhisana-ai/cli
Dhisana CLI
Agent-friendly command line access to Dhisana lead, account, campaign, signal monitoring, and Agentic Flow workflow APIs. The CLI is intentionally dependency-light so tools such as OpenClaw, Codex, or other agent harnesses can shell out to deterministic JSON commands.
Install locally
cd dhisana-cli
npm link
Or run without linking:
node bin/dhisana.js --help
Requires Node.js 18+ for built-in fetch.
Configuration
Every command accepts flags or environment variables:
| Flag | Environment variable | Default |
|---|---|---|
--base-url |
DHISANA_API_BASE_URL |
https://api-agent.dhisana.ai |
--api-key |
DHISANA_API_KEY |
none |
--token |
DHISANA_API_TOKEN |
none |
--agent-instance-id |
DHISANA_AGENT_INSTANCE_ID |
none |
--agent-instance-name |
DHISANA_AGENT_INSTANCE_NAME |
none |
API key auth is preferred and is sent as X-API-Key: <key>.
--token is still supported for legacy bearer-token flows and is sent as
Authorization: Bearer <token>.
The API key can be supplied in raw or pasted-header formats:
dhisana lead lookup --api-key dhisana_key_id.secret --agent-instance-id <uuid> --email buyer@example.com
dhisana lead lookup --api-key "X-API-Key: dhisana_key_id.secret" --agent-instance-id <uuid> --email buyer@example.com
dhisana lead lookup --api-key "Authorization: ApiKey dhisana_key_id.secret" --agent-instance-id <uuid> --email buyer@example.com
Agent instance selection can use an id or an exact name:
dhisana signal review-inbox --agent-instance-id <uuid>
dhisana signal review-inbox --agent-instance-name "Service Cloud RFP"
If neither is provided, the CLI calls GET /v1/agent-instances and automatically uses the only available agent instance. If the API key can access multiple agent instances, pass --agent-instance-id or --agent-instance-name.
Environment variable aliases are also accepted for agent harnesses:
DHISANA_API_KEYDHISANA_CLI_API_KEYAGENTOPS_API_KEY
Bearer-token aliases remain:
DHISANA_API_TOKENDHISANA_ACCESS_TOKENAGENTOPS_API_TOKEN
Commands
Lookup leads
dhisana lead lookup --agent-instance-id <uuid> --id <lead_uuid>
dhisana lead lookup --agent-instance-id <uuid> --email buyer@example.com
dhisana lead lookup --agent-instance-id <uuid> --linkedin-url https://www.linkedin.com/in/example
dhisana lead lookup --agent-instance-id <uuid> --lead-list-id <list_uuid> --search "Jane"
This maps to AgentOps endpoints:
GET /v1/agent-instances/{agent_instance_id}/leads/{lead_id}GET /v1/agent-instances/{agent_instance_id}/leadsGET /v1/agent-instances/{agent_instance_id}/lead-lists/{lead_list_id}/leads
Add leads to a list
Add existing leads:
dhisana lead add-to-list --agent-instance-id <uuid> --lead-list-id <list_uuid> --lead-ids <lead1>,<lead2>
Create new leads in the list:
dhisana lead add-to-list \
--agent-instance-id <uuid> \
--lead-list-id <list_uuid> \
--name "Jane Buyer" \
--email jane@example.com \
--linkedin-url https://www.linkedin.com/in/jane-buyer \
--title "VP Sales" \
--company "Acme"
Bulk import from a JSON file:
dhisana lead add-to-list --agent-instance-id <uuid> --lead-list-id <list_uuid> --file leads.json --ignore-duplicates
This maps to:
POST /v1/agent-instances/{agent_instance_id}/lead-lists/{lead_list_id}/add-leadsPOST /v1/agent-instances/{agent_instance_id}/lead-lists/{lead_list_id}/leads/bulk- Optional
--from-extensionuses/leads/bulk_from_extension
Update leads
Update selected lead fields:
dhisana lead update \
--agent-instance-id <uuid> \
--id <lead_uuid> \
--name "Jane Buyer" \
--title "VP Sales" \
--company "Acme"
Update from JSON, without overwriting populated fields:
dhisana lead update --agent-instance-id <uuid> --id <lead_uuid> --file lead-update.json --only-if-empty
This maps to:
PUT /v1/agent-instances/{agent_instance_id}/leads/{lead_id}
Manage lead tags and notes
dhisana lead add-tag --agent-instance-id <uuid> --id <lead_uuid> --tags hot,enterprise
dhisana lead remove-tag --agent-instance-id <uuid> --lead-ids <lead1>,<lead2> --tags stale
dhisana lead add-note --agent-instance-id <uuid> --id <lead_uuid> --content "Follow up next week"
dhisana lead update-note --agent-instance-id <uuid> --id <lead_uuid> --note-id <note_uuid> --content "Updated note"
dhisana lead remove-note --agent-instance-id <uuid> --id <lead_uuid> --note-id <note_uuid>
Note content can also be supplied with --file <path> or --stdin.
This maps to:
POST /v1/agent-instances/{agent_instance_id}/leads/bulk-tagsPOST /v1/agent-instances/{agent_instance_id}/leads/{lead_id}/notesPUT /v1/agent-instances/{agent_instance_id}/leads/{lead_id}/notes/{note_id}DELETE /v1/agent-instances/{agent_instance_id}/leads/{lead_id}/notes/{note_id}
Lookup accounts
dhisana account lookup --agent-instance-id <uuid> --id <account_uuid>
dhisana account lookup --agent-instance-id <uuid> --domain snowflake.com
dhisana account lookup --agent-instance-id <uuid> --name "Snowflake"
dhisana account lookup --agent-instance-id <uuid> --ai --prompt "the cloud data platform company that IPO'd in 2020"
This maps to:
GET /v1/agent-instances/{agent_instance_id}/accounts/{account_id}GET /v1/agent-instances/{agent_instance_id}/accounts/searchPOST /v1/agent-instances/{agent_instance_id}/accounts/ai-lookup
Add accounts
dhisana account add \
--agent-instance-id <uuid> \
--name "Acme" \
--domain acme.com \
--website https://www.acme.com \
--industry "Software"
Bulk import:
dhisana account add --agent-instance-id <uuid> --file accounts.json --bulk --ignore-duplicates
This maps to:
POST /v1/agent-instances/{agent_instance_id}/accountsPOST /v1/agent-instances/{agent_instance_id}/accounts/bulk
Update accounts
Update selected account fields:
dhisana account update \
--agent-instance-id <uuid> \
--id <account_uuid> \
--name "Acme" \
--domain acme.com \
--industry "Software"
Update from JSON, without overwriting populated fields:
dhisana account update --agent-instance-id <uuid> --id <account_uuid> --file account-update.json --only-if-empty
This maps to:
PUT /v1/agent-instances/{agent_instance_id}/accounts/{account_id}
Manage account tags and notes
dhisana account add-tag --agent-instance-id <uuid> --id <account_uuid> --tags target,enterprise
dhisana account remove-tag --agent-instance-id <uuid> --account-ids <account1>,<account2> --tags stale
dhisana account add-note --agent-instance-id <uuid> --id <account_uuid> --content "Procurement team owns this account"
dhisana account update-note --agent-instance-id <uuid> --id <account_uuid> --note-id <note_uuid> --content "Updated note"
dhisana account remove-note --agent-instance-id <uuid> --id <account_uuid> --note-id <note_uuid>
Note content can also be supplied with --file <path> or --stdin.
This maps to:
POST /v1/agent-instances/{agent_instance_id}/accounts/bulk-tagsPOST /v1/agent-instances/{agent_instance_id}/accounts/{account_id}/notesPUT /v1/agent-instances/{agent_instance_id}/accounts/{account_id}/notes/{note_id}DELETE /v1/agent-instances/{agent_instance_id}/accounts/{account_id}/notes/{note_id}
Add tasks
Create a task for selected leads:
dhisana task add \
--agent-instance-id <uuid> \
--name "Validate emails" \
--task-type validate_email \
--lead-ids <lead1>,<lead2>
Create a task for a filtered selection:
dhisana task add \
--agent-instance-id <uuid> \
--name "Research target accounts" \
--task-type research \
--select-all \
--filter '{"status":"new"}' \
--lead-list-id <list_uuid>
Look up tasks:
dhisana task lookup --agent-instance-id <uuid> --id <task_uuid>
dhisana task lookup --agent-instance-id <uuid> --limit 25
This maps to:
POST /v1/agent-instances/{agent_instance_id}/tasks/GET /v1/agent-instances/{agent_instance_id}/tasks/GET /v1/agent-instances/{agent_instance_id}/tasks/{task_id}
List campaigns
dhisana campaign list --agent-instance-id <uuid>
dhisana campaign list --agent-instance-id <uuid> --active
dhisana campaign list --agent-instance-id <uuid> --status PAUSED --limit 100
This maps to:
GET /v1/agent-instances/{agent_instance_id}/campaigns/
The AgentOps campaign list API is paginated and does not currently expose a server-side status filter, so --active and --status filter the returned page locally. Increase --limit when an agent needs to scan more campaigns.
Get campaign performance
Fetch one campaign by ID:
dhisana campaign performance --agent-instance-id <uuid> --campaign-id <campaign_uuid>
Fetch performance for active campaigns in the current page:
dhisana campaign performance --agent-instance-id <uuid> --active --limit 100
The response includes:
summary: compact campaign health for agent harnesses, including send counts, unique leads contacted, engagement KPIs, link click rates, and historical daily rowscampaign: raw campaign metadatacounter: raw campaign counter from AgentOpslink_tracking: raw link tracking summary, unless--skip-link-trackingis provided
Useful options:
dhisana campaign performance --agent-instance-id <uuid> --campaign-id <campaign_uuid> --include-bots
dhisana campaign performance --agent-instance-id <uuid> --active --skip-link-tracking
This maps to:
GET /v1/agent-instances/{agent_instance_id}/campaigns/{campaign_id}GET /v1/agent-instances/{agent_instance_id}/campaigns/{campaign_id}/counterGET /v1/agent-instances/{agent_instance_id}/campaigns/{campaign_id}/link-tracking/summary
Invoke an Agentic Flow workflow
Run a workflow once by ID:
dhisana workflow run --agent-instance-id <uuid> --workflow-id <workflow_uuid>
Run a workflow once by exact name, with parameters:
dhisana workflow run \
--agent-instance-id <uuid> \
--workflow-name "Test Workflow" \
--parameters '{"company":"Acme"}'
Start from scratch instead of resuming saved offsets:
dhisana workflow run --agent-instance-id <uuid> --workflow-name "Test Workflow" --from-scratch
Useful options:
dhisana workflow run --agent-instance-id <uuid> --workflow-name "Test Workflow" --record-limit 10
dhisana workflow run --agent-instance-id <uuid> --workflow-name "Test Workflow" --run-as-user-id <user_uuid>
dhisana workflow run --agent-instance-id <uuid> --workflow-id <workflow_uuid> --parameters-file params.json
dhisana workflow run --agent-instance-id <uuid> --workflow-id <workflow_uuid> --offset-overrides '{"apollo-source":0}'
The response includes:
summary: workflow id, name, returned status, whether the start was accepted, from-scratch flag, record limit, run-as user, and whether parameters/offset overrides were sentworkflow: raw Agentic Flow payload returned by AgentOps
This maps to:
GET /v1/agent-instances/{agent_instance_id}/smart-lists/{workflow_id}GET /v1/agent-instances/{agent_instance_id}/smart-lists/when resolving by namePUT /v1/agent-instances/{agent_instance_id}/smart-lists/{workflow_id}when persisting--record-limitor--run-as-user-idPOST /v1/agent-instances/{agent_instance_id}/smart-lists/{workflow_id}/start?clear_offset=<true|false>
List Signal Monitoring Review inbox items
dhisana signal review-inbox --agent-instance-id <uuid>
dhisana signal review-inbox --agent-instance-id <uuid> --limit 100 --sort-by priority_score
dhisana signal review-inbox --agent-instance-id <uuid> --item-type candidate --review-status OPEN --routing-status OPEN
dhisana signal review-inbox --agent-instance-id <uuid> --assigned-scope my_groups --reviewer-group-id <group_uuid>
Common filters:
dhisana signal review-inbox --agent-instance-id <uuid> --signal-type rfp_watch --object-type account
dhisana signal review-inbox --agent-instance-id <uuid> --policy-id <policy_uuid>
dhisana signal review-inbox --agent-instance-id <uuid> --source-provider opengov --rfp-status active
The response includes:
summary: compact Review inbox counts and item summaries for agent harnesses- raw
items,by_review_status,by_routing_status, andby_policyfrom AgentOps
Get one Signal Monitoring Review inbox item
dhisana signal review-item --agent-instance-id <uuid> --item-id <item_uuid> --item-type candidate
dhisana signal review-item --agent-instance-id <uuid> --review-assignment-id <assignment_uuid>
Use --item-type signal, --item-type candidate, or --item-type opportunity_rollup when the item type is known. If omitted, the backend searches all Review inbox item types.
This maps to:
GET /v1/agent-instances/{agent_instance_id}/signal-review-queue
Extending
Add a command by creating a module under src/commands/ and registering it in src/cli.js.
Each command receives:
{
(options, config, fetchImpl);
}
Use DhisanaApiClient from src/apiClient.js so auth, base URL normalization, JSON parsing, and errors stay consistent.