Use when user needs complex data queries, multi-entity joins, aggregations, or analysis across Affinity data. Also use when user wants to filter, group, sort, or aggregate CRM records programmatically. Triggers: "query language", "structured query", "SQL-like", "find all persons where", "count opportunities by", "sum deal values", "average amount", "group by status", "filter AND/OR", "include companies with persons".
/plugin marketplace add yaniv-golan/affinity-sdk/plugin install mcp@xaffinityThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill covers the structured query language for querying Affinity CRM data via the query MCP tool.
Use the query tool instead of individual CLI commands when you need:
For simple lookups, prefer execute-read-command with individual commands.
{
"$version": "1.0",
"from": "persons",
"where": { "path": "email", "op": "contains", "value": "@acme.com" },
"include": ["companies", "opportunities"],
"select": ["id", "firstName", "lastName", "email"],
"orderBy": [{ "field": "lastName", "direction": "asc" }],
"limit": 100
}
| Field | Description |
|---|---|
from | Entity type: persons, companies, opportunities, listEntries, interactions, notes |
| Field | Description |
|---|---|
$version | Query format version (default: "1.0") |
where | Filter conditions |
include | Related entities to fetch |
select | Fields to return (default: all) |
orderBy | Sort order |
groupBy | Field to group by (requires aggregate) |
aggregate | Aggregate functions to compute |
having | Filter on aggregate results |
limit | Maximum records to return |
| Operator | Description | Example |
|---|---|---|
eq | Equal | {"path": "status", "op": "eq", "value": "Active"} |
neq | Not equal | {"path": "status", "op": "neq", "value": "Closed"} |
gt | Greater than | {"path": "amount", "op": "gt", "value": 10000} |
gte | Greater than or equal | {"path": "amount", "op": "gte", "value": 10000} |
lt | Less than | {"path": "amount", "op": "lt", "value": 5000} |
lte | Less than or equal | {"path": "amount", "op": "lte", "value": 5000} |
| Operator | Description | Example |
|---|---|---|
contains | Contains substring | {"path": "email", "op": "contains", "value": "@gmail"} |
starts_with | Starts with | {"path": "name", "op": "starts_with", "value": "Acme"} |
| Operator | Description | Example |
|---|---|---|
in | Value in list | {"path": "status", "op": "in", "value": ["New", "Active"]} |
between | Value in range | {"path": "amount", "op": "between", "value": [1000, 5000]} |
contains_any | Array contains any | {"path": "tags", "op": "contains_any", "value": ["vip", "hot"]} |
contains_all | Array contains all | {"path": "tags", "op": "contains_all", "value": ["verified", "active"]} |
| Operator | Description | Example |
|---|---|---|
is_null | Field is null | {"path": "email", "op": "is_null"} |
is_not_null | Field is not null | {"path": "email", "op": "is_not_null"} |
{
"where": {
"and_": [
{ "path": "status", "op": "eq", "value": "Active" },
{ "path": "amount", "op": "gt", "value": 10000 }
]
}
}
{
"where": {
"or_": [
{ "path": "email", "op": "contains", "value": "@acme.com" },
{ "path": "email", "op": "contains", "value": "@acme.io" }
]
}
}
{
"where": {
"not_": { "path": "status", "op": "eq", "value": "Closed" }
}
}
Fetch related entities in a single query:
{
"from": "persons",
"include": ["companies", "opportunities"],
"limit": 50
}
| From | Can Include |
|---|---|
persons | companies, opportunities, interactions, notes, listEntries |
companies | people, opportunities, interactions, notes, listEntries |
opportunities | people, companies, interactions |
lists | entries |
listEntries | entity (dynamically resolves to person/company/opportunity based on entityType) |
Important: Note that companies and opportunities use people (not persons) as the relationship name.
{
"from": "opportunities",
"aggregate": {
"total": { "count": true },
"totalValue": { "sum": "amount" },
"avgValue": { "avg": "amount" },
"minValue": { "min": "amount" },
"maxValue": { "max": "amount" }
}
}
{
"from": "opportunities",
"groupBy": "status",
"aggregate": {
"count": { "count": true },
"totalValue": { "sum": "amount" }
}
}
{
"from": "opportunities",
"groupBy": "status",
"aggregate": {
"count": { "count": true }
},
"having": { "path": "count", "op": "gte", "value": 5 }
}
listEntries requires either listId or listName filter:
// By ID
{"from": "listEntries", "where": {"path": "listId", "op": "eq", "value": 12345}}
// By name (executor resolves name → ID at runtime)
{"from": "listEntries", "where": {"path": "listName", "op": "eq", "value": "Dealflow"}}
Invalid paths: list.name, list.id - use listName or listId directly.
Note: When using listName, the query executor looks up the list by name and resolves it to a listId before fetching entries. This adds one API call but allows using human-readable names.
When querying listEntries with groupBy, aggregate, or where on fields.* paths, the query engine automatically detects which fields are referenced and requests their values from the API.
{
"from": "listEntries",
"where": {"path": "listName", "op": "eq", "value": "Dealflow"},
"groupBy": "fields.Status",
"aggregate": {"count": {"count": true}}
}
To select all custom fields, use fields.* wildcard in select:
{
"from": "listEntries",
"where": {"path": "listId", "op": "eq", "value": 12345},
"select": ["id", "entityId", "fields.*"],
"limit": 50
}
Access nested fields using dot notation:
{
"from": "listEntries",
"where": { "path": "fields.Status", "op": "eq", "value": "Active" }
}
Common paths:
fields.<FieldName> - Custom list fields on listEntriesfields.* - All custom fields (wildcard, use in select)emails[0] - First email in arraycompany.name - Nested object field (on included relationships){
"from": "interactions",
"where": { "path": "created_at", "op": "gte", "value": "-30d" }
}
Supported formats:
-30d - 30 days ago+7d - 7 days from nowtoday - Start of todaynow - Current timeyesterday - Start of yesterdaytomorrow - Start of tomorrowAlways use dry-run first to preview expensive queries:
{
"query": {
"from": "persons",
"include": ["companies", "opportunities"]
},
"dryRun": true
}
Returns execution plan with:
{
"from": "persons",
"where": {
"and_": [
{ "path": "email", "op": "is_not_null" },
{ "path": "fields.VIP", "op": "eq", "value": true }
]
},
"include": ["companies"],
"orderBy": [{ "field": "lastName", "direction": "asc" }],
"limit": 100
}
{
"from": "listEntries",
"where": { "path": "listId", "op": "eq", "value": 12345 },
"groupBy": "fields.Status",
"aggregate": {
"count": { "count": true },
"totalValue": { "sum": "fields.Deal Value" }
}
}
{
"from": "interactions",
"where": {
"and_": [
{ "path": "created_at", "op": "gte", "value": "-7d" },
{ "path": "type", "op": "in", "value": ["call", "meeting"] }
]
},
"include": ["persons"],
"orderBy": [{ "field": "created_at", "direction": "desc" }],
"limit": 50
}
{
"from": "companies",
"where": {
"or_": [
{ "path": "lastInteraction.date", "op": "lt", "value": "-90d" },
{ "path": "lastInteraction.date", "op": "is_null" }
]
},
"limit": 100
}
| Parameter | Type | Default | Description |
|---|---|---|---|
query | object | required | The JSON query object |
dryRun | boolean | false | Preview execution plan without running |
maxRecords | integer | 1000 | Safety limit (max 10000) |
timeout | integer | 120 | Query timeout in seconds |
maxOutputBytes | integer | 50000 | Truncation limit for results |
listEntries field filters happens client-sideCreating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.