Changelog¶
Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
[1.8.0] - 2026-03-10¶
Highlights¶
entry field --get now returns resolved person and company objects (with firstName, lastName, primaryEmailAddress, etc.) instead of raw integer IDs. This matches the behavior of list export and provides a consistent, AI-agent-friendly experience. Under the hood, the command switched from V1 to V2 API.
CLI Plugin 1.6.1¶
Added¶
- Skill: documented file commands (
company files,person files,opportunity files) withls,download,read,uploadsubcommands — prevents unnecessary fallback to raw v1 API calls - Skill: added file commands to Quick Reference table
Changed¶
- Breaking:
entry field --getoutput for person/company reference fields now returns resolved objects instead of raw integer IDs. Scripts that parse the integer ID should update to read from the resolved object. entry field --getnow uses the V2 API (/v2/lists/{listId}/list-entries/{entryId}/fields) instead of V1field_values.list()
Fixed¶
ListEntryService.get_field_values()(sync and async) now correctly parses V2 API list response format — previously returned empty results due to dict/list type mismatch- Added
typestoREPEATABLE_QUERY_PARAMSso the V2 fields endpointtypesfilter parameter is correctly encoded FieldValues._coerce_from_apino longer double-wraps dicts that are already in{requested, data}format — fixesfields.*returning null after round-trip serialization- Query executor now warns (via
ctx.warnings) when field metadata fetch fails instead of silently returning null for allfields.*values - Query executor now warns when field name resolution fails in
whereclauses instead of silently skipping resolution - Multi-parent fetch path now normalizes list entry fields —
fields.*inselectworks correctly when query spans multiple parent lists
[1.7.3] - 2026-03-08¶
Highlights¶
UserId is now a subtype of PersonId, reflecting that workspace users are internal persons in Affinity's data model. Code that receives a UserId (e.g., Note.creator_id) can now pass it directly to any API accepting PersonId — no casting required.
Changed¶
UserIdnow extendsPersonIdinstead ofIntId—isinstance(UserId(1), PersonId)is nowTrue. This is an intentional runtime behavior change: workspace users are internal persons in Affinity's data model. Code that usedisinstanceto distinguish users from persons should useperson.type == PersonType.INTERNALinstead.- Simplified internal casts that previously converted
UserId→int→PersonId
Documentation¶
- data-model.md: clarified that user IDs and person IDs share the same ID space
[1.7.2] - 2026-03-03¶
Highlights¶
The xaffinity CLI plugin's SessionStart hook no longer runs pip install on every container start. Installation is deferred to first actual use via a self-installing wrapper, cutting session startup from ~30s to <1s in ephemeral environments like Cowork. Skill descriptions now display correctly in the Claude Code UI. Also documents two Affinity API limitations discovered via support: interaction entity association and enriched field constraints.
CLI Plugin 1.6.0¶
Added¶
- Lazy install: SessionStart hook now drops a lightweight self-installing wrapper instead of running
pip installunconditionally. The wrapper defers installation to first actual xaffinity use, with mkdir-based locking for concurrent safety, a marker file for install failure detection, and a 45s wait timeout for concurrent invocations. - PreToolUse hook: Install-failure detection via
$HOME/.xaffinity-install-statusmarker — blocks commands with a clear error when pip install failed, instead of cryptic failures. - PreToolUse hook: Lazy session cache start —
xaffinity session startis triggered on first xaffinity command instead of at session start, withCLAUDE_ENV_FILEpersistence.
Changed¶
- SessionStart timeout reduced from 60s to 10s (hook is now lightweight)
- PreToolUse Bash timeout increased from 10s to 60s (accommodates pip install via wrapper on first use)
- SessionStart status message updated to "Preparing Affinity CLI environment..."
Fixed¶
- Skill YAML frontmatter: replaced multi-line
>scalar with single-line description (Claude Code couldn't parse folded scalars, showing>instead of the description) - Skill: updated session cache documentation to reflect lazy initialization
Documentation¶
- Skill: documented that interactions cannot be associated with companies/opportunities via API (UI's "Also add to" feature has no API equivalent)
- Skill: documented that "Current Organization" is read-only via API and "Current Job Title" requires a separate
field updateafter person creation
SDK Plugin 1.5.5¶
Fixed¶
- Skill YAML frontmatter: replaced multi-line
>scalar with single-line description (same parsing fix as CLI plugin)
Documentation¶
- Data model: documented interaction entity association limitation and enriched field constraints (Current Organization, Current Job Title)
[1.7.1] - 2026-03-01¶
Highlights¶
Plugin skills now guide agents to consolidate multi-source CRM queries into single scripts instead of running separate commands that dump raw JSON into the conversation. The CLI plugin also auto-starts session caching at session begin, so metadata is shared across all commands without manual setup.
CLI Plugin 1.5.6¶
Added¶
- Skill: "Multi-Source Tasks" section guiding agents to write consolidated bash scripts (with session cache + jq) instead of running separate CLI commands that dump raw JSON into context
- Skill: "Extract only what you need" guidance with jq examples for single-command output filtering
- Skill: session cache fallback instruction for environments without automatic setup
- SessionStart hook: automatic session cache initialization (
xaffinity session start) withCLAUDE_ENV_FILEpersistence for Cowork
SDK Plugin 1.5.4¶
Added¶
- Skill: "Multi-Source Tasks: Output Only the Summary" section guiding agents to print concise summaries instead of raw
model_dump_json()output when combining multiple data sources
[1.7.0] - 2026-02-21¶
Highlights¶
New field history-bulk command fetches field change history across an entire list in one shot — useful for pipeline stage analysis, funnel conversion, and time-in-stage metrics. Also fixes --dotenv to search upward for .env files (matching standard dotenv behavior) and hardens the PreToolUse hook with a three-tier API key check.
Added¶
- CLI:
field history-bulkcommand for batch field change history across lists. Supports--list-id(with--all/--max-resultsbounding),--list-entry-idsfor specific entries,--action-typefiltering,--dry-runfor API cost estimation, and concurrent fetching viaXAFFINITY_CONCURRENCYenv var. Partial failures are reported as warnings without blocking successful entries. - CLI:
--listalias for--list-idonfield lsandfield history-bulkcommands (LLM-friendly shorthand). - MCP Plugin: Pipeline history analysis skill (
pipeline-history) with 5-step workflow: identify status field, export current state, dry-run estimate, fetch history, analyze transitions.
Fixed¶
- CLI:
--dotenvnow searches upward for.envfiles usingfind_dotenv(usecwd=True), matching standard python-dotenv behavior. Previously only checked the current working directory, which failed in Cowork VM sessions where the working directory differs from the.envlocation. - CLI Plugin: PreToolUse hook (
pre-xaffinity.sh) now uses three-tier API key detection: (1)AFFINITY_API_KEYenv var, (2)--dotenvcheck-key, (3) plain check-key (config.toml). Previously only checked the env var, blocking commands when the key was configured via.envor config file.
Documentation¶
- Added
list export --jsonoutput structure documentation to data model resource (fieldkey details,--field/--field-typerequirement). - Added
field history-bulkto MCP command registry for LLM discoverability. - Added field change history section to data model resource.
CLI Plugin 1.5.5¶
Fixed¶
- PreToolUse hook: three-tier API key detection (env var → dotenv → config.toml)
MCP Plugin 1.19.0¶
Added¶
- Pipeline history analysis skill for deal stage transition workflows
[1.6.2] - 2026-02-17¶
Highlights¶
Update notifications now work reliably. Previously, having multiple Python environments (e.g., dev virtualenv + system install) could silently poison the update cache, and the first config update-check run always said "never checked" instead of just checking.
Fixed¶
- CLI:
config update-checknow checks PyPI inline when cache is missing or stale, instead of showing "never checked" - CLI: Background update worker now receives version from spawning process, fixing version mismatch when multiple Python environments share a cache
- CLI: Smarter cache invalidation — upgrading no longer discards the cache; it adapts it
[1.6.1] - 2026-02-16¶
Highlights¶
config check-key --env-file <path> now correctly reads the specified file instead of only looking in the current directory. Fixes key discovery in Cowork VM sessions where the .env is on a mounted path.
Fixed¶
- CLI:
config check-keyignored--env-fileflag — always checkedCWD/.envinstead of the user-provided path
[1.6.0] - 2026-02-16¶
Highlights¶
New --include-me flag for interaction create auto-includes your person ID. Also fixes field writes for person/company multi-value fields -- entity IDs are now wrapped correctly for the V2 API, and --append properly merges instead of replacing.
Added¶
- CLI:
interaction create --include-meflag to auto-include current user's person ID via whoami - CLI: Enhanced validation error hint for interaction person_ids constraint (internal/external requirement)
Changed¶
- Added interaction create guidance to CLI plugin skill
- Added interaction create examples to MCP data model resource
Fixed¶
- CLI: Person/company field writes (
--set,--append) now correctly wrap entity IDs in{"id": <int>}for the V2 API. Previously sent raw integers, causingvalidation_error: value at /value/data is not null. - CLI:
--appendonperson-multi/company-multifields now merges with existing values instead of silently replacing them (same fix already existed fordropdown-multi). - CLI: Repeated
--appendon the same multi-value field (e.g.,--append Tags A --append Tags B) now aggregates into a single write, preventing the second write from overwriting the first. - CLI: V1 API
allows_multiplepromotion now applies topersonandcompanyfield types (previously onlydropdownwas promoted to its-multivariant). - MCP command registry: corrected
--participantsto--person-idforinteraction create
Plugin Releases — 2026-02-15¶
Plugin versions are now independent from the SDK version (see VERSIONING.md).
CLI Plugin 1.5.3¶
Changed¶
- Plugin versions are now independent from SDK version
Fixed¶
- Security: SessionStart hook no longer exports
AFFINITY_API_KEYto the environment. The key stays in.envand is read per-command via--dotenv, preventing the LLM from accessing it viaenvorecho $AFFINITY_API_KEY.
SDK Plugin 1.5.3¶
Changed¶
- Rewrote
affinity-python-sdkskill description following Anthropic skills guide formula
MCP Plugin 1.18.1¶
Changed¶
- Rewrote
query-languageSKILL.md (861→293 lines) with progressive disclosure; extracted detail to 4 reference files - Updated
affinity-mcp-workflowsskill description following guide formula
[1.5.2] - 2026-02-14¶
Highlights¶
Plugin improvements for Claude Code and Cowork: automatic environment setup via SessionStart hook, API key protection via .env read guard, and clearer plugin names in marketplace listings.
Added¶
- CLI Plugin: SessionStart hook (
session-setup.sh) for Cowork bootstrap — installs xaffinity, sets PATH, loads API key from.env - CLI Plugin: PreToolUse/Read guard (
guard-env-read.sh) blocks reading.envfiles to prevent API key exposure in conversation - CLI Plugin: Query command reference (
references/query-guide.md) - MCP Plugin: Query language reference files (
references/filter-operators.md,quantifiers.md,include-expand.md,output-formats.md)
Changed¶
- Marketplace: Renamed plugins from generic "sdk"/"cli"/"mcp" to "Affinity CRM SDK (unofficial)", "Affinity CRM CLI (xaffinity, unofficial)", "Affinity CRM MCP (unofficial)" for clarity in plugin lists.
- CLI Plugin: Rewrote skill description following Anthropic skills guide formula
- MCP Plugin: Rewrote
query-languageSKILL.md (861→293 lines) with progressive disclosure; extracted detail toreferences/ - MCP Plugin: Updated
affinity-mcp-workflowsskill description following guide formula - SDK Plugin: Updated
affinity-python-sdkskill description following guide formula
Removed¶
- CLI Plugin: Removed
/affinity-helpcommand (redundant — CLI skill auto-triggers on relevant prompts)
Fixed¶
- CLI Plugin:
.envparsing now handles quoted values and CRLF line endings - CLI Plugin: SessionStart hook writes to
CLAUDE_ENV_FILEare idempotent (no duplicate lines on re-run)
[1.5.1] - 2026-02-12¶
Highlights¶
Fixes writing to multi-select dropdown fields. --set and --append now correctly handle the array format required by the API, and --append properly merges with existing selections instead of replacing them.
Fixed¶
- CLI:
--setand--appendnow work correctly for dropdown-multi fields when V1 API returnsvalue_type="dropdown"withallows_multiple=True. Previously,resolve_dropdown_valueonly checkedvalue_type(notallows_multiple) to determine the payload format, sending{"dropdownOptionId": ID}instead of[{"dropdownOptionId": ID}]. - CLI:
--appendfor dropdown-multi fields now merges with existing values instead of replacing them. The V2 API replaces the entire option array on POST, so--appendnow reads existing selections, adds the new option (deduplicating), and sends the combined array.
[1.5.0] - 2026-02-11¶
Highlights¶
Interaction queries are now much more robust. Date ranges are validated up front, ranges over 1 year are auto-chunked seamlessly, and mixing naive/timezone-aware datetimes is caught with a clear error. Breaking: InteractionService.list() now requires start_time, end_time, and an entity ID -- callers already getting 422 errors will now get a clear SDK-level message instead.
Changed¶
- Breaking:
InteractionService.list()andAsyncInteractionService.list()now requirestart_time,end_time, and at least one entity ID (person_id,company_id, oropportunity_id). Previously these were optional, but the API always rejected calls without them (422). Callers that were passingNonefor these parameters were already getting API errors; this change surfaces the requirement at the SDK level with clear error messages. InteractionService.list()now validates date ranges:start_timemust be beforeend_time, and the range must not exceed 365 days.
Added¶
- SDK:
InteractionService.iter()andAsyncInteractionService.iter()now automatically chunk date ranges exceeding 365 days. Large ranges are split into <=365-day chunks with synthetic cursors bridging them, making iteration seamless. - SDK:
iter()defaultsend_timetodatetime.now(timezone.utc)when not provided, so callers only need to specifystart_time. - SDK: Timezone consistency validation — mixing naive and timezone-aware datetimes raises
ValueErrorwith guidance instead of a rawTypeError. - SDK:
_chunk_date_range()utility for splitting date ranges into API-compatible chunks. - CLI: Query executor now properly fetches interactions for
includeandexpandoperations. Previously, these paths calledinteractions.list()without requiredtype/date parameters and silently returned empty results.
Fixed¶
- SDK: Fixed falsy truthiness bugs in
InteractionService.list()wherePersonId(0),CompanyId(0),page_size=0, and emptypage_token=""were incorrectly dropped. Changedif x:toif x is not None:for all optional parameters.
[1.4.1] - 2026-02-11¶
Highlights¶
CLI errors from invalid commands or options now produce proper JSON error envelopes when --json is active, fixing broken downstream JSON parsers.
Fixed¶
- CLI: Click-level errors (unknown commands, invalid options) now emit a proper JSON error envelope (
{"ok": false, "error": {...}}) when--jsonor--output jsonis active. Previously, stdout was empty and only plain text went to stderr, breaking downstream JSON parsers. - CLI:
normalize_exception()now handlesclick.UsageError(→usage_error, exit code 2) andclick.ClickException(→error, exit code from exception) instead of misclassifying them asinternal_error.
[1.4.0] - 2026-02-10¶
Highlights¶
FieldResolver now resolves all value types to human-readable text -- not just dropdowns, but also persons, companies, locations, and interactions. Dropdown fields return rich DropdownOption objects with .text, .rank, and .color. Breaking: get_value() returns DropdownOption instead of raw IDs, and resolve_dropdowns parameter is replaced by ResolveMode.
Changed¶
- Breaking:
FieldValues.get_value()now returnsDropdownOptionobjects for dropdown/ranked-dropdown fields instead of rawintIDs. TheDropdownOptionhas.id,.text,.rank, and.colorattributes. This applies to all dropdown value types (dropdown,ranked-dropdown,dropdown-multi). - Breaking: Removed
FieldType.LIST_SPECIFIC. UseFieldType.LISTinstead. The V2 API uses"list"uniformly;"list-specific"was never a valid V2 value. - Breaking:
FieldResolver.get()parameterresolve_dropdowns: boolreplaced withresolve: ResolveMode(ResolveMode.RAWorResolveMode.TEXT).ResolveMode.TEXTresolves dropdowns, persons, companies, and locations to human-readable strings.
Added¶
- SDK:
ResolveModeenum (RAW,TEXT) for controllingFieldResolvervalue resolution. - SDK:
ResolveMode.TEXTnow resolves person, company, location, and interaction fields to human-readable strings (not just dropdowns). - SDK:
FieldResolversupports source-qualified field names ("dealroom:Description") for disambiguating enrichment fields with the same display name. Ambiguous bare names emit a one-time warning at access time. - SDK:
validate_entity_field_types()raisesValueErrorwhenFieldType.LISTis passed to company/person endpoints (which only acceptENRICHED,GLOBAL,RELATIONSHIP_INTELLIGENCE). - SDK:
DropdownOption.colornow accepts bothint(V1) andstr(V2) values.
Fixed¶
- SDK:
FieldValues._extract_value()now correctly recurses through{"data": {...}}envelopes for dropdown values, fixing ranked-dropdown and dropdown-multi extraction that previously returned the raw envelope dict. - SDK: Dropdown text resolution now works without V1 field metadata. Previously,
FieldResolverbuilt a lookup table fromFieldMetadata.dropdown_options(V1-only; always empty on V2). Now text is read directly from theDropdownOptionobject extracted from the field value. - SDK:
async_check_unreplied()replaced broken no-typeiter()call withasyncio.gather()per-type pattern for parallel interaction fetching. - CLI:
_extract_person_display_nameand_extract_person_namenow delegate to sharedresolve_person()utility.
Documentation¶
- Added
FieldResolverusage example to README "Working with Lists" section. - Added
FieldResolvermention to getting-started guide field gotchas, linking to performance guide. - Replaced raw field count with
FieldResolverusage inexamples/basic_usage.py. - Updated field-types-and-values guide: dropdown types now document
DropdownOptionreturn type. - Added "List Entry Field Access" section to performance guide explaining
entry.entity.fieldsdelegation.
[1.3.2] - 2026-02-08¶
Highlights¶
Fixes writing to dropdown-multi fields via --set and --append. Previously all dropdown-multi writes failed with a type mismatch error.
Fixed¶
- CLI:
--setand--appendnow work fordropdown-multifields. Previously,resolve_dropdown_value()only handleddropdownandranked-dropdown, causing all dropdown-multi writes to fail with "Field value type should be dropdown-multi" errors. The fix resolves option text/IDs and wraps them in the array format required by the V2 API.
[1.3.1] - 2026-02-06¶
Highlights¶
FieldResolver now works correctly with list entry objects -- it auto-delegates to the inner entity when fields were fetched on the entity rather than the list entry directly.
Fixed¶
- SDK:
FieldResolver.get()andget_by_id()now automatically delegate to the inner entity when called with aListEntryWithEntitywhose fields were not directly requested. Previously warned and returnedNoneeven whenentry.entity.fieldswere populated. - SDK:
FieldResolverduplicate field name warning now includes enrichment source (e.g., "dealroom" vs "affinity-data") instead of raw IDs, making enrichment provider collisions immediately clear.
[1.3.0] - 2026-02-06¶
Highlights¶
New list_batch() method on AsyncEntityFileService for fetching files across multiple entities concurrently with auto-pagination.
Added¶
- SDK:
list_batch()onAsyncEntityFileServicefor fetching files across multiple entities concurrently with auto-pagination. Acceptsperson_ids,company_ids, oropportunity_idswithmax_concurrentandon_error("raise"/"skip") parameters.
[1.2.0] - 2026-02-06¶
Highlights¶
New async batch methods for files and reminders: batch_get() on AsyncEntityFileService and list_batch() on AsyncReminderService for concurrent multi-entity operations. Also fixes falsy ID bugs across reminder and file services.
Added¶
- SDK:
batch_get()onAsyncEntityFileServicefor fetching file metadata concurrently with controlled concurrency. Supportsmax_concurrentandon_error("raise"/"skip") parameters. - SDK:
list_batch()onAsyncReminderServicefor fetching reminders across multiple entities concurrently with auto-pagination. Acceptsperson_ids,company_ids, oropportunity_idswith common filters.
Fixed¶
- SDK: Fixed truthiness bugs in
ReminderService.list(),AsyncReminderService.list(),EntityFileService.list(), andAsyncEntityFileService.list()wherePersonId(0),CompanyId(0), and other falsy values were silently dropped from query parameters. Changed allif <param>:guards toif <param> is not None:. - SDK: Fixed incorrect
ReminderStatusdocstrings that referenced non-existent "SNOOZED" and "COMPLETE" values. The actual enum values areCOMPLETED,ACTIVE, andOVERDUE.
[1.1.0] - 2026-02-05¶
Highlights¶
Major convenience release: FieldResolver for looking up field values by name instead of ID, batch_get() for concurrent entity fetching, read_only() factory for safe clients, and get_first() for quick single-entity lookups. Working with Affinity field data is now significantly easier.
Added¶
- SDK:
CompanyService.get_many(company_ids)convenience method for batch fetching multiple companies in a single API call. Alias forlist(ids=[...])with better discoverability. - SDK:
FieldValues.get(field_id)convenience method for safer access to field values by ID. Returns the field value dict orNoneif not found. - SDK:
Affinity.read_only()andAffinity.read_only_from_env()factory methods (and async variants) for creating clients that block all write operations. Useful for read-only scripts and dashboards. - SDK:
get_first()convenience method onCompanyService,PersonService,OpportunityService,ListService, andListEntryService(sync + async). Returns the first matching entity orNone. - SDK:
batch_get()onAsyncCompanyService,AsyncPersonService, andAsyncOpportunityServicefor fetching multiple entities with controlled concurrency. Supportsmax_concurrentandon_error("raise"/"skip") parameters. - SDK:
FieldValues.get_value(field_id)for extracting the unwrapped field value (e.g., returns"Active"instead of{"data": "Active"}). Handles text, dropdown, multi-value, and location fields. - SDK:
FieldResolverhelper class for looking up field values by name instead of ID. Supports case-insensitive matching, batch extraction viaget_many(), and dropdown option resolution. - Docs: "Field Lookup Patterns" section in performance guide documenting
FieldResolverusage, low-level access, and field metadata sources.
Fixed¶
- SDK:
FieldService.list()now caches results for 5 minutes (matching V2 field endpoints). Previously only V2 endpoints (companies.get_fields(),persons.get_fields()) were cached. - SDK:
FieldService.list(list_id=ListId(0))no longer skips thelist_idparameter. The falsy ID guard (if list_id:) was changed toif list_id is not None:.
[1.0.3] - 2026-02-03¶
Highlights¶
Fixes MCP timeout failures during large query include operations by emitting incremental progress every 10 records instead of only at start/end.
Fixed¶
- CLI: Query
includeoperations now emit incremental progress every 10 records during N+1 API calls. Previously, progress was only emitted at the start and end of include steps, causing MCP timeout to expire during long-running N+1 operations (e.g., 100 records withinclude: ["persons"]). The fix changes fromasyncio.gather()toasyncio.as_completed()for incremental progress emission.
[1.0.2] - 2026-02-03¶
Highlights¶
Fixes descending sort for string fields in queries (dates, names were silently returning ascending order) and corrects API call estimates for sorted queries that could cause premature timeouts.
Fixed¶
- CLI: Query
orderBywith descending direction now works correctly for string values (dates, names, etc.). Previously, descending sort on non-numeric fields silently returned results in ascending order because string values cannot be negated. The fix uses a comparison-inverting wrapper class for proper descending sort. - CLI: Query dry run now correctly estimates API calls when
orderByis present. Previously,estimatedApiCallsused thelimitvalue even though sorting requires fetching all records first. A query withlimit: 200andorderByon a 9000-record list showed 4 API calls instead of ~90. This caused dynamic timeout calculations to be too short.
[1.0.1] - 2026-02-01¶
Highlights¶
Fixes unreplied email/chat detection (--check-unreplied and expand: ["unreplied"]) which was silently failing, and adds required type parameter validation to InteractionService with clear error messages instead of cryptic 422s.
Fixed¶
- SDK:
InteractionService.list()anditer()now validate thattypeparameter is required, raisingValueErrorwith clear guidance instead of failing with cryptic API 422 errors. The Affinity V1 API has always required this parameter. - CLI:
--check-unrepliedandexpand: ["unreplied"]now work correctly. Previously, all unreplied checks silently failed (returning null) due to missingtypeparameter in interaction API calls. - CLI: Query
dryRunJSON output now includes a warning when estimated API calls exceed 50, helping LLMs notice expensive operations.
[1.0.0] - 2026-01-31¶
First stable release. The SDK and CLI APIs are now considered stable. Breaking changes will follow semantic versioning (major version bumps).
Added¶
- CLI: Auto-update notifications. The CLI now checks PyPI daily (in background) and displays a notification when a new version is available. Notifications are suppressed in non-interactive environments (
--quiet,--output json, CI, no TTY). Disable withXAFFINITY_NO_UPDATE_CHECK=1orupdate_check = falsein config. - CLI:
xaffinity config update-check --backgroundflag for non-blocking update checks. Used by MCP server to trigger background updates without waiting for results.
Fixed¶
- MCP: Add
AFFINITY_API_KEYto environment variable allowlist. API keys configured in Claude Desktop's MCP settings were being silently dropped, requiring manual config file creation as a workaround. - MCP: Detect CLI in macOS Python framework path (
/Library/Frameworks/Python.framework/Versions/*/bin/). Users who installed viapip installwith python.org Python were getting "xaffinity: command not found" errors.
[0.16.1] - 2026-01-27¶
Added¶
- Docs: "Verify Installation" section in CLI documentation with step-by-step health check instructions (
xaffinity --version,AFFINITY_API_KEY=... xaffinity whoami).
[0.16.0] - 2026-01-26¶
Changed (Breaking)¶
- SDK:
Person.interactionsandCompany.interactionsattribute type changed fromdict[str, Any]to typedInteractionsmodel. Migration: change dict access (person.interactions["last_event"]["person_ids"]) to attribute access (person.interactions.last_event.person_ids). TheInteractionsmodel provides typed access tofirst_email,last_email,first_event,last_event,next_event,last_chat_message, andlast_interactionfields, each containing anInteractionEventwithdateandperson_idsattributes.
Fixed¶
- SDK:
PersonService.get()field values are now properly validated asFieldValuemodels.
[0.15.1] - 2026-01-25¶
Fixed¶
- CLI:
query --output jsonnow supports cursor-based pagination when output is truncated. Previously, JSON truncation happened at the MCP layer after CLI execution, so no cursor was emitted. Now JSON truncation happens in the CLI layer (same as other formats), enabling proper resumption via--cursor.
[0.15.0] - 2026-01-23¶
Added¶
- CLI:
company files read,person files read,opportunity files readcommands to read file content with chunking support. Returns base64-encoded content with metadata (size,offset,length,hasMore,nextOffset). Use--offsetand--limitto fetch large files in chunks. Default chunk size is 1MB.
Fixed¶
- CLI:
--env-filenow implicitly enables dotenv loading when an explicit file path is provided (not the default.env). Previously, using--env-file .sandbox.envwithout--dotenvwould silently ignore the file. - CLI: Fixed dropdown field value resolution in
entry field --setand--appendcommands. The V2 API requires{"dropdownOptionId": ID}format, but the CLI was sending raw values. Now dropdown text (e.g., "In Progress") or numeric IDs are properly resolved to the V2 API format. - CLI: List field metadata now fetched from V1 API to include
dropdown_options, which V2 API omits.
0.14.0 - 2026-01-22¶
Added¶
- CLI:
company files ls,person files ls,opportunity files lscommands to list files attached to entities without downloading them. Supports pagination (--page-size,--cursor,--max-results,--all), selector resolution (ID, URL, name, domain, email), and MCP safety limits. - CLI:
file-urlcommand to get presigned download URLs for files. Returns URL valid for 60 seconds along with file metadata (name, size, contentType). Useful for programmatic access and MCP workflows. - CLI:
files download --file-idoption for single-file downloads. Downloads one file directly without creating a manifest. - SDK:
FilesService.get_download_url(file_id)method to get presigned download URLs without downloading content. ReturnsPresignedUrlwith URL, file metadata, and expiration info. - SDK:
PresignedUrldataclass exported fromaffinitypackage with fields:url,file_id,name,size,content_type,expires_in,expires_at.
Changed¶
- CLI:
files dumprenamed tofiles downloadfor clarity.
Fixed¶
- SDK:
CompanyService.get()andPersonService.get()now automatically fall back to V1 API when V2 returns 404. This handles V1→V2 eventual consistency issues where a search (company ls --query,person ls --query) finds an entity via V1, but a subsequentgetfails because V2 hasn't synced yet. The fallback is transparent and requires no code changes.
Known Issues¶
- MCP:
get-file-urltool returns valid presigned URLs, but Claude Desktop's WebFetch cannot accessuserfiles.affinity.codue to domain sandbox restrictions. This affects all Claude Desktop users - neither "Additional allowed domains" nor "All domains" settings work around this limitation (#19087, #11897). Workaround: Usefiles readcommand (returns content inline), copy URL to browser, or use CLI directly withfiles download --file-id.
0.13.1 - 2026-01-21¶
Fixed¶
- CLI: Query progress now emits
progress: 0inon_step_startevents, enabling mcp-bash timeout extension for slow first-page fetches
0.13.0 - 2026-01-21¶
Changed (Breaking)¶
- SDK:
AffinityList.list_sizefield removed. The V2 API returns incorrect values (often 0 for non-empty lists). Useclient.lists.get_size(list_id)instead, which fetches from the V1 API and caches for 5 minutes.
Added¶
- SDK:
ListService.get_size(list_id)andAsyncListService.get_size(list_id)methods to get accurate list size from the V1 API with automatic caching (5 minutes). Useforce=Trueto bypass cache when fresh data is critical.
0.12.0 - 2026-01-20¶
Added¶
- CLI:
query --cursoroption for resumable pagination when responses are truncated (exceed--max-output-bytes): - Streaming mode (simple queries): O(1) resumption via stored Affinity API cursor - no re-fetching of previous pages
- Full-fetch mode (queries with orderBy/aggregate): Results cached to disk, zero API calls on resume
- Cursor emitted to stderr as NDJSON
{"type": "cursor", "cursor": "...", "mode": "..."}for MCP extraction - CLI exits with code 100 when truncated (cursor available)
- Cache auto-cleanup on startup: LRU eviction (500MB limit), 1-hour TTL
- Validation: query hash, format mismatch, cache tampering all detected with clear errors
0.11.0 - 2026-01-20¶
Added¶
- CLI: Full scan protection when running via MCP gateway. Commands with pagination (
list export,person ls,company ls, etc.) now enforce limits: default 1000 records, max 10000 records. The--allflag is blocked with a clear error message guiding users to use explicit--max-resultsor cursor pagination instead.
Changed¶
- CLI:
--csvis now an alias for--output csv(consistent with--jsonbeing an alias for--output json) - CLI: CSV sub-options (
--csv-bom,--csv-header,--csv-mode) auto-enable CSV output when no format is specified - CLI: Error messages for output flag conflicts no longer have trailing periods (e.g.,
--csv and --json are mutually exclusiveinstead of ending with.)
Removed¶
- Breaking: CLI:
--prettyflag removed fromquerycommand (use| jq .for pretty JSON output)
Fixed¶
- CLI: Query engine now correctly computes
entityNamefor Person list entries. Previously,entityNamewasnullfor persons because the API returnsfirstName/lastNameinstead ofname. Now uses the same display name logic aslist export. - CLI: Table/CSV formatters now correctly detect Person entities with real API types (
external/internal) or no type field. Previously, formatters only checked fortype="person"which the API never actually returns, causing Person data to display as "object (N keys)" instead of "John Smith (id=123)".
Performance¶
- CLI: Session cache now caches person resolution (by email/name) and company resolution (by domain/name), reducing API calls when running multiple commands in a session pipeline.
- CLI: Person and company field resolution now uses session cache, avoiding redundant field definition fetches.
0.10.0 - 2026-01-19¶
Fixed¶
- SDK: Removed unsafe
asyncio.create_task()inAsyncAffinity.__del__that could cause tasks to be garbage collected before completion. Users must now use context managers or callclose()explicitly. - SDK: Added thread-safe locking to
SimpleCachefor concurrent access. - SDK: Fixed async client initialization race condition with asyncio lock.
- SDK: Fixed stream context manager cleanup on errors in HTTP client.
- SDK: Fixed empty/whitespace content handling in JSON response parsing.
- SDK: Added error handling for partial file cleanup on download failures.
- SDK: Improved filename sanitization for server-provided filenames (null bytes, control chars).
- CLI: Added validation for
--timeout,--max-columns,--max-records,--max-output-bytesto reject non-positive values. - CLI: Added validation for
--env-fileto check file exists when--dotenvis enabled. - CLI: Added file size limit (1MB) for config files to prevent memory exhaustion.
- CLI: Added file permission warnings for API key files (same as config files).
- CLI: Fixed TOML escaping to handle newlines, tabs, and carriage returns in API keys.
- CLI: Fixed JSON/TOML parse error handling with helpful error messages.
- CLI: Fixed type validation for list access in CSV/table output (verify first element is dict).
- CLI: Fixed exponential backoff calculation to cap exponent before computing power.
- CLI: Let
AuthenticationError/AuthorizationErrorpropagate in query executor instead of silent catch. - CLI: Added logging to silent exception handlers in entity validation.
Changed (Breaking)¶
- CLI:
--check-unreplied-emailsrenamed to--check-unrepliedwith support for both email and chat messages. - New
--unreplied-typesflag: comma-separated list of types to check (email,chat,all). Default:email,chat - Output now includes
typefield ("email" or "chat") in addition to date, daysSince, and subject - Chat messages have
nullsubject (no subject attribute) - Cross-type reply detection: email replied via chat (or vice versa) counts as "replied"
- CLI: Query
expand: ["unrepliedEmails"]renamed toexpand: ["unreplied"] - Same cross-type reply detection and multi-type support as CLI flag
- CSV columns renamed:
unrepliedEmailDate→unrepliedDate,unrepliedEmailDaysSince→unrepliedDaysSince,unrepliedEmailSubject→unrepliedSubject, plus newunrepliedTypecolumn
0.9.14 - 2026-01-19¶
Fixed¶
- CLI: Query
selectclause now automatically includesexpandfields. Previously, usingselectwithexpandwould filter out the expansion data (e.g.,interactionDates,unreplied), requiring users to explicitly list expansions inselect.
0.9.13 - 2026-01-19¶
Added¶
- CLI: Query
includeforlistEntriesnow supportspersons,companies,opportunities, andinteractions. Fetches related entities based on list entry entity type (e.g., company entries get associated persons). - CLI: Query
expand: ["unrepliedEmails"]now works withlistEntries. Checks each list entry's underlying entity for unreplied incoming emails. - CLI: Query extended include syntax with parameters:
{include: {interactions: {limit: 50, days: 180}}}- Limit and lookback control{include: {opportunities: {list: "Pipeline"}}}- Scope to specific opportunity list{include: {persons: {where: {...}}}}- Filter included entities
Fixed¶
- CLI: Query TOON format now correctly flattens
fields.*andinteractionDateslike markdown and CSV formats do. Previously, TOON was missing the_apply_explicit_flattening()call, causing nested fields to be truncated and interaction dates to be missing entirely. - CLI:
list export --check-unreplied-emailsnow works standalone without requiring--expand. - CLI:
expand: ["interactionDates"]now always produces 8 canonical columns regardless of data presence, ensuring consistent schema across all records. - SDK: Improved error message when adding wrong entity type to a list (e.g., adding a company to a person list). Now provides clear guidance about list type requirements instead of exposing raw API validation error.
Performance¶
- CLI: Query
expand: ["interactionDates"]is significantly faster with parallel person resolution, bounded concurrent fetches (semaphore=10), and parallelized section resolution. Default concurrency increased from 5 to 15 (tunable viaXAFFINITY_QUERY_CONCURRENCY).
0.9.12 - 2026-01-17¶
Changed¶
- CLI: Query
listEntriesnow normalizes reference field values to display strings: - Person fields:
{"firstName": "Jane", "lastName": "Doe"}→"Jane Doe" - Company fields:
{"name": "Acme Corp", "id": 123}→"Acme Corp" - Multi-person/company fields: Arrays of names instead of raw objects
- Use
expandorincludefor full entity data when needed - CLI: Query output with explicit
selectonfields.*paths now flattens fields to top-level columns in table/CSV/markdown formats. JSON output preserves nested structure.
0.9.11 - 2026-01-17¶
Added¶
- CLI: Query
includeclause now displays included relationships inline by default. Use--include-style=separatefor separate tables or--include-style=ids-onlyfor raw IDs. JSON output includes fullincludedandincluded_by_parentmappings for correlation. - CLI: Query
includeextended syntax for custom display fields:include: {companies: {display: ["name", "domain"]}}. - SDK:
with_interaction_datesandwith_interaction_personsparameters forCompanyService.get()andPersonService.get(). When enabled, routes to V1 API to fetch interaction date summaries (last/next meeting, email dates, team member IDs). - CLI:
--expand interactionsoption forlist exportcommand. Adds interaction date summaries to each list entry (last meeting, next meeting, last email, last interaction with daysSince/daysUntil calculations and team member names). Supports both JSON and CSV output formats. - CLI:
expand: ["interactionDates"]support inquerycommand. Enriches records with interaction date summaries directly on each record inresult.data. Works withpersons,companies, andlistEntriesqueries. - CLI:
--check-unreplied-emailsflag forlist exportcommand. Detects unreplied incoming emails for each list entry and adds date, daysSince, and subject to output. Use--unreplied-lookback-daysto configure lookback period (default: 30 days). - CLI:
--with-interaction-datesand--with-interaction-personsflags forcompany getandperson getcommands. Fetches interaction date summaries directly in entity get operations. - MCP: Query tool
formatparameter now functional (was previously ignored). Supportstoon,markdown,json,jsonl,csv. - CLI:
--max-output-bytesoption forquerycommand. Enables format-aware truncation for MCP use, returning exit code 100 when truncated. - CLI:
format_toon_envelope()function for TOON output with full envelope (data[N]{...}:,pagination:,included_*:sections). - SDK: Batch association methods for PersonService, CompanyService, and OpportunityService:
get_associated_company_ids_batch(person_ids)/get_associated_opportunity_ids_batch(person_ids)get_associated_person_ids_batch(company_ids)/get_associated_opportunity_ids_batch(company_ids)get_associated_person_ids_batch(opportunity_ids)/get_associated_company_ids_batch(opportunity_ids)- All return
dict[EntityId, list[AssocId]]withon_error="raise"|"skip"parameter. - SDK:
retriesparameter onpersons.get(),companies.get(), andopportunities.get()methods. Enables automatic retry with exponential backoff on 404 errors to handle V1→V2 eventual consistency after create operations. Default isretries=0(fail fast). - CLI: Reminder date options now accept relative dates and keywords in addition to ISO-8601:
--due-date:+7d,+2w,+1m,+1y,today,tomorrow,yesterday,now--due-after,--due-before: Same formats for filtering inreminder ls- Example:
xaffinity reminder create --due-date +7d --type one-time --owner-id 123 - CLI: Domain validation for
company createandcompany updatenow provides helpful error messages: - Detects underscores (RFC 1035 violation) and suggests dash replacement
- Detects URL prefixes and extracts domain
- Example:
--domain test_company.com→ "Use 'test-company.com' instead" - Docs: V1→V2 eventual consistency guide covering 404 after create and stale data after update scenarios.
- Tests: Integration test suite for SDK write operations (
tests/integration/).
Changed¶
- SDK:
OpportunityService.get_associated_people()andget_associated_companies()now use V2 batch lookup instead of individual V1 fetches, reducing N+1 API calls (e.g., 50 people now fetched in 2 calls instead of 51). - SDK: Query executor
_batch_fetch_by_ids()now uses V2 batch lookup for persons and companies, improving query performance on relationship includes. - CLI: Query
includeclause now fetches relationship IDs in parallel, then batch-fetches full records via V2 API, reducing API calls from N×M to N+1 for deduped lookups.
Changed (Breaking)¶
- MCP: Query tool default format changed from
jsontotoonfor better token efficiency (~40% fewer tokens). - CLI: TOON query output now includes full envelope structure instead of data-only format. The
dataprefix is added to the array header:data[N]{fields}:instead of[N]{fields}:.
Fixed¶
- MCP: Query tool now honors the
formatparameter instead of always using JSON output. - SDK:
ListEntryService.batch_update_fields()now uses correct V2 API payload format. Previously failed with "Missing discriminator for property operation" error.
0.9.9 - 2026-01-14¶
Added¶
- CLI:
querycommand now supports advanced relationship filtering: allquantifier: Filter where all related items match a condition (e.g., find persons where all their companies have ".com" domains)nonequantifier: Filter where no related items match a condition (e.g., find persons with no spam interactions)existssubquery: Filter where at least one related item exists, optionally matching a condition (e.g., find persons who have email interactions)_countpseudo-field: Filter by count of related items (e.g.,"path": "companies._count", "op": "gte", "value": 2)- Available relationship paths: persons→companies/opportunities/interactions/notes/listEntries, companies→persons/opportunities/interactions/notes/listEntries, opportunities→persons/companies/interactions
- Note: These features cause N+1 API calls to fetch relationship data; use
--dry-runto preview
Changed (Breaking)¶
- CLI: Renamed relationship
"people"to"persons"for consistency with entity type names: - Query
include:{"from": "companies", "include": ["people"]}→ use["persons"] - CLI
--expand:xaffinity company get <id> --expand people→ use--expand persons - JSON output:
data.people→data.persons
Fixed¶
- CLI: Query engine no longer silently passes all records for
all,none, and_countfilters. Previously these were placeholder implementations that returnedTruefor all records, causing incorrect query results. (Bug #15)
0.9.8 - 2026-01-12¶
Fixed¶
- CLI:
querycommand now correctly fetches all records before applying filter, sort, or aggregate operations. Previously, limits were applied during fetch which caused incorrect results: - With filters: Empty results when matching records were beyond the limit position
- With sort + limit: Random N records sorted instead of actual top N
- With aggregate: Inaccurate counts/sums computed on partial data
0.9.7 - 2026-01-12¶
Fixed¶
- CI: Smoke test now correctly installs CLI extras before testing CLI import
0.9.6 - 2026-01-12¶
Added¶
- CLI:
listEntriesqueries now include convenience aliases:listEntryId,entityId,entityName,entityType. These intuitive field names work in bothselectandwhereclauses. - CLI:
listEntriesrecords now always include afieldskey (defaults to{}if no custom fields).
Changed¶
- CLI: Query projection now includes null values for explicitly selected fields. Previously,
select: ["entityName", "fields.Status"]would return{}if Status was null; now returns{"entityName": "Acme", "fields": {"Status": null}}.
0.9.5 - 2026-01-12¶
Added¶
- CLI: New
--output/-ooption supporting multiple formats:json,jsonl,markdown,toon,csv,table(default). markdown: GitHub-flavored markdown tables, best for LLM analysis and comprehensiontoon: Token-Optimized Object Notation, 30-60% fewer tokens than JSON for large datasetsjsonl: JSON Lines format, one object per line for streaming workflows- Example:
xaffinity person ls --output markdown,xaffinity query -o toon - Existing
--csvand--jsonflags continue to work as before.
Changed¶
- CLI:
to_cell()now extracts "text" from dropdown/multi-select fields instead of JSON-serializing the full dict. This makes CSV and other tabular outputs human-readable for dropdown values.
Fixed¶
- CLI:
querycommand withlimitnow correctly returns results when combined with client-side filters (likehas_anyon multi-select fields). Previously, the limit was applied during fetch before filtering, causing empty results when the first N records didn't match the filter criteria.
0.9.4 - 2026-01-12¶
Added¶
- CLI:
querycommand now supportshas_anyandhas_alloperators for multi-select field filtering. - SDK/CLI: Filter parser now supports V2 API comparison operators:
>,>=,<,<=for numeric/date comparisons. - SDK/CLI: Filter parser now supports word-based operator aliases for LLM/human clarity:
contains,starts_with,ends_with(string matching)gt,gte,lt,lte(numeric/date comparisons)is null,is not null,is empty(null/empty checks)- SDK/CLI: Filter parser now supports collection bracket syntax
[A, B, C]with operators: in [A, B]- value is one of the listed valuesbetween [1, 10]- value is in range (inclusive)has_any [A, B]- array field contains any of the valueshas_all [A, B]- array field contains all of the valuescontains_any [A, B]- substring match for any termcontains_all [A, B]- substring match for all terms= [A, B]- set equality (array has exactly these elements)=~ [A, B]- V2 API collection contains (array contains all elements)
Fixed¶
- CLI:
querycommand now correctly filters on multi-select dropdown fields (like "Team Member"). Theeqoperator checks array membership for scalar values and set equality for array values. Previously, these queries returned 0 results due to strict equality comparison. - SDK/CLI:
list export --filternow correctly matches multi-select dropdown fields. The=,!=, and=~operators now handle array values properly. Also fixes extraction of text values from multi-select dropdown API responses. - SDK/CLI: Fixed
=^(starts_with) and=$(ends_with) operators which were broken due to tokenizer ordering issue.
Improved¶
- SDK/CLI: Filter parser now provides helpful hints for common mistakes:
- Multi-word field names: suggests quoting (
"Team Member") - Multi-word values: suggests quoting (
"Intro Meeting") - SQL keywords (
AND,OR): suggests correct symbols (&,|) - Double equals (
==): suggests single=
0.9.3 - 2026-01-11¶
Changed¶
- CI: SDK releases now include MCPB bundle and plugin ZIP for convenience.
- CI: Enabled PyPI attestations via workflow_dispatch API trigger.
0.9.2 - 2026-01-11¶
Fixed¶
- SDK:
AsyncListEntryService.pages()now supportsprogress_callbackparameter (sync/async parity fix).
Changed¶
- BREAKING: CLI:
interaction lsJSON output restructured for consistency: .data.interactions→.data(direct array).data.metadata.totalRows→.meta.summary.totalRows.data.metadata.dateRange→.meta.summary.dateRange.data.metadata.typeStats→.meta.summary.typeBreakdown- BREAKING: CLI:
note lsJSON output restructured for consistency: .data.notes→.data(direct array)- Pagination:
.data.notes.nextCursor→.meta.pagination.nextCursor - BREAKING: CLI:
queryJSON output (with--include-meta) restructured for consistency: .meta.recordCount→.meta.summary.totalRows- Included entity counts now in
.meta.summary.includedCounts - CLI: Standardized
ResultSummaryfooter rendering across all commands (displays row counts, date ranges, type breakdowns as compact footer text instead of tables).
0.9.1 - 2026-01-11¶
Added¶
- CLI:
querycommand now validates entity queryability and provides clear error messages for unsupported entities. - CLI:
querycommand resolves field names to IDs automatically (e.g.,"field": "Status"works alongside"fieldId": 123).
Fixed¶
- CLI:
queryforlistEntriesentity now correctly requireslistIdfilter. - CLI:
queryrelationship definitions now correctly setrequires_n_plus_1flag for proper query planning.
0.9.0 - 2026-01-11¶
Added¶
- CLI: New
querycommand for structured JSON queries with complex filtering, includes, aggregations, and sorting. Use--dry-runto preview execution plans. Supports entities: persons, companies, opportunities, listEntries, interactions, notes. - MCP: New
querytool for complex data queries via JSON query language. Supports filtering (AND/OR/NOT), includes (related entities), aggregations (count/sum/avg/min/max), groupBy, and sorting. - CLI:
--limitalias for--max-resultsoncompany get,person get, andopportunity getcommands (consistency withlscommands).
Changed¶
- CLI:
--list,--list-entry-field, and--show-list-entry-fieldsnow auto-imply--expand list-entriesoncompany getandperson getcommands (improved DX).
0.8.6 - 2026-01-10¶
Added¶
- SDK:
PersonService.get_associated_company_ids()andget_associated_opportunity_ids()methods for symmetric association API. - SDK:
CompanyService.get_associated_opportunity_ids()method.
0.8.5 - 2026-01-10¶
Fixed¶
- CLI/SDK:
FilterParseErrornow raised when filter expressions fail to parse (previously silently ignored). Common cause: unquoted multi-word values like--filter 'Status=Intro Meeting'must be quoted:--filter 'Status="Intro Meeting"'. - CLI: Pre-commit hook now validates installed CLI version matches
pyproject.tomlbefore regenerating MCP registry.
0.8.4 - 2026-01-10¶
Added¶
- CLI: NDJSON progress output for
interaction lsmulti-type queries (MCP integration).
0.8.3 - 2026-01-10¶
Added¶
- CLI:
--limitalias for--max-resultson alllscommands (LLM-friendly). - CLI: Option aliases now included in
--help --jsonoutput.
0.8.2 - 2026-01-10¶
No user-facing changes. Version bump for PyPI release.
0.8.1 - 2026-01-10¶
No user-facing changes. Version bump for PyPI release.
0.8.0 - 2026-01-10¶
Changed¶
- CLI: Renamed
--jsonto--set-jsononperson field,company field,opportunity fieldcommands to avoid conflict with global--jsonoutput flag. - BREAKING: CLI:
--csv FILEis now--csvflag that outputs CSV to stdout. Use shell redirection:--csv > file.csv. Applies toperson ls,company ls,opportunity ls,list export. - CLI:
--csvand--jsonare now mutually exclusive (error if both specified). - CLI:
person ls --queryandcompany ls --querynow support--fieldand--field-typeoptions via hybrid V1→V2 fetch. - BREAKING: CLI:
interaction lsdate filter parameters renamed for consistency: --start-time→--after--end-time→--before- Context metadata keys changed:
startTime/endTime→after/before - Note: Interaction object fields (
startTime/endTime) are unchanged - BREAKING: CLI:
interaction lsnow requires--type(was optional but API required it). - CLI:
interaction lsdate range now defaults to all-time when--daysand--afterare omitted. - BREAKING: CLI:
interaction lsremoved--cursorand--allflags (auto-chunking replaces manual pagination). - BREAKING: CLI:
interaction lsoutput field renamedmodifiers.type→modifiers.types(now always an array). - BREAKING: CLI:
interaction lsmetadatachunksProcessedmoved totypeStats[type].chunksProcessed. - BREAKING: CLI: Naive datetime strings (without timezone) are now interpreted as local time instead of UTC. Use explicit
Zsuffix or offset for UTC. See datetime-handling guide for details. - BREAKING: CLI: List entry field commands unified into single
entry fieldcommand: entry set-field --field F --value V→entry field --set F Ventry set-field --field F --value-json '{...}'→entry field --set-json '{"F": ...}'entry set-fields --updates-json '{...}'→entry field --set-json '{...}'entry unset-field --field F→entry field --unset Fentry unset-field --field F --value V→entry field --unset-value F Ventry unset-field --field F --all-values→entry field --unset F- Removed:
--field-idoption (field IDs can be passed as FIELD argument directly) - Behavior change:
--seton multi-value field now replaces all values (use--appendto add)
Added¶
- CLI:
interaction ls --typenow accepts multiple types (e.g.,--type email --type meeting). - CLI:
interaction ls --type allconvenience option fetches all interaction types. - CLI:
interaction lsmulti-type results sorted by date descending (types interleaved). - CLI:
interaction lsmetadata includestypeStatswith per-type counts and chunk info. - CLI:
interaction lsauto-chunking for date ranges > 1 year (transparently splits into API-compatible chunks). - CLI:
interaction ls --days Nconvenience flag for "last N days" queries. - CLI:
interaction ls --csvand--csv-bomflags for CSV export. - CLI:
interaction lsmetadata in JSON output includesdateRange,typeStats,totalRows. - SDK:
ListEntryService.from_saved_view()now acceptsfield_idsandfield_typesparameters. - CLI:
list export --saved-viewcan now be combined with--fieldfor server-side filtering with explicit field selection. - SDK:
idsparameter added toPersonService,CompanyService, andOpportunityServicefor batch fetching by ID. - CLI:
entry field --get FIELDfor reading field values (new functionality). - CLI:
entry field --append FIELD VALUEfor adding to multi-value fields without replacing. - CLI:
entry field --unset-value FIELD VALUEfor removing specific value from multi-value field.
Fixed¶
- SDK:
FieldValuesnow properly parses field arrays from API responses (previously showedrequested=False).
Removed¶
- CLI:
person searchandcompany searchcommands. Useperson ls --queryandcompany ls --queryinstead. - CLI: Removed
entry set-field,entry set-fields, andentry unset-fieldcommands (replaced by unifiedentry field).
0.7.0 - 2026-01-08¶
Added¶
- CLI: Column limiting for wide table output - tables now auto-limit columns based on terminal width.
- CLI:
--all-columnsflag to show all columns regardless of terminal width. - CLI:
--max-columns Nflag for fine control over column limits. - CLI: Real-time filter scanning progress during
list export --filtershows "Scanning X... (Y matches)". - CLI: Export summary line after filtered operations (e.g., "Exported 35 rows (filtered from 9,340 scanned) in 2:15").
- CLI:
format_duration()helper for human-readable time formatting. - CLI: Rich pager now uses
styles=Trueto preserve ANSI colors when paging. - SDK:
FilterStatsdataclass for tracking scanned/matched counts during filtered pagination. - SDK:
PaginatedResponse.filter_statsproperty exposes filter statistics.
Fixed¶
- CLI: JSON progress output no longer appears alongside Rich progress bar (mutual exclusivity enforced).
- SDK: Dropdown field filtering now extracts "text" property from dropdown dicts.
0.6.11 - 2026-01-07¶
Added¶
- CLI: Parameter help text (
help) now included in--help --jsonoutput. - CLI: Click.Choice values (
choices) now included in--help --jsonoutput. - CLI: Examples from docstrings now parsed and included in
--help --jsonoutput.
Changed¶
- CLI: Improved
--filterhelp text with full operator list (= != =~ =^ =$ > < >= <=). - CLI: Improved
--queryhelp text to clarify V1 fuzzy search vs V2 structured filtering.
0.6.10 - 2026-01-06¶
Added¶
- CLI: JSON progress output to stderr when not connected to a TTY (for MCP integration).
- CLI:
@progress_capabledecorator to mark commands supporting progress reporting. - CLI: Rate-limited progress updates (0.65s interval) with guaranteed 100% completion emission.
- CLI:
progressCapablefield in--help --jsonoutput for registry generation. - MCP:
run_xaffinity_with_progresshelper for progress-aware CLI execution. - MCP:
command_supports_progresshelper to check registry for progress capability. - MCP: Execute tools now forward CLI progress for
@progress_capablecommands. - CLI: File upload commands (
person/company/opportunity files upload) marked as@progress_capable. - MCP:
PROGRESS_MIN_VERSIONin COMPATIBILITY for graceful degradation with older CLIs. - MCP:
version_gtehelper for portable version comparison (macOS/Linux). - MCP: CLI version check in
command_supports_progress(disables progress for CLI < 0.6.10). - MCP:
XAFFINITY_CLI_VERSIONexported for tool scripts to access CLI version.
Changed¶
- MCP: Updated mcp-bash.lock to patched v0.9.3 (commit ee245a7) with progress passthrough fixes.
0.6.9 - 2026-01-06¶
Changed¶
- CLI Plugin: Skill now documents destructive command confirmation flow (look up, ask, wait, execute with
--yes). - CLI Plugin: Skill lists all destructive commands requiring double confirmation.
0.6.8 - 2026-01-05¶
Added¶
- CLI:
@categoryand@destructivedecorators for MCP registry generation. - CLI:
--help --jsonoutput for machine-readable command documentation. - CLI: Commands now expose category (read/write/local) and destructive metadata.
0.6.7 - 2026-01-03¶
Changed¶
- Docs: Updated all documentation links to use versioned
/latest/URLs for reliable navigation.
0.6.6 - 2026-01-03¶
Fixed¶
- SDK: Regex patterns in
lists.pyandhttp.pywere double-escaped, matching literal\dinstead of digits.
0.6.5 - 2026-01-02¶
No user-facing changes. Version bump for PyPI release.
0.6.4 - 2026-01-02¶
No user-facing changes. Version bump for PyPI release.
0.6.3 - 2026-01-02¶
No user-facing changes. Version bump for PyPI release.
0.6.2 - 2026-01-02¶
Fixed¶
- CLI: Added explicit
type=strto Click arguments for Python 3.13 mypy compatibility.
0.6.1 - 2026-01-02¶
Changed¶
- Docs: Separated MCP Server documentation from Claude Code plugins.
0.6.0 - 2026-01-02¶
Added¶
- MCP:
read-xaffinity-resourcetool for clients with limited resource support.
Changed¶
- Plugins: Restructured into 3-plugin marketplace architecture (affinity-sdk, xaffinity-cli, xaffinity-mcp).
- Docs: Restructured Claude integrations with consistent naming.
Fixed¶
- CLI: Improved
setup-keycommand UX with Rich styling. - MCP: Source both
.zprofileand.zshrcin environment wrapper. - MCP: Parse JSON response correctly for
check-keyoutput.
0.5.1 - 2026-01-01¶
Fixed¶
- Plugins: Consolidated plugin structure and fixed relative paths.
0.5.0 - 2026-01-01¶
Added¶
- MCP: Initial xaffinity MCP server as separate Claude Code plugin.
- CLI: Top-level
entrycommand group as shorthand forlist entry(e.g.,xaffinity entry getinstead ofxaffinity list entry get). - CLI:
--query/-qflag forperson ls,company ls, andopportunity lsto enable free-text search (V1 API). - CLI:
--company-idand--opportunity-idoptions forinteraction ls. - CLI:
-Ashort flag for--allon all paginated list commands. - CLI:
-nshort flag for--max-resultson all commands with result limits. - CLI:
-sshort flag for--page-sizeon all pagination commands. - CLI:
-tshort flag for--typeon interaction commands. - CLI: Structured
CommandContextfor all commands. - SDK:
OpportunityService.search(),search_pages(),search_all()methods for V1 opportunity search. - SDK: Async versions of opportunity search methods in
AsyncOpportunityService. - SDK:
InteractionService.list()now acceptscompany_idandopportunity_idparameters.
Changed¶
- CLI:
list viewrenamed tolist getfor consistency with other entity commands. - CLI:
--completed/--not-completedboolean flag pattern forreminder update(replaces separate flags). - CLI: Removed API version mentions from help text (implementation detail).
- CLI:
interaction lsnow requires an entity ID (--person-id,--company-id, or--opportunity-id) and defaults to last 7 days with visible warning (API max: 1 year). - CLI: Unified
person field,company field,opportunity fieldcommands replaceset-field,set-fields, andunset-fieldcommands. New syntax:--set FIELD VALUE,--unset FIELD,--set-json '{...}',--get FIELD. - CLI: Note content separated from metadata in table display.
Removed¶
- CLI:
person set-field,person set-fields,person unset-fieldcommands (useperson fieldinstead). - CLI:
company set-field,company set-fields,company unset-fieldcommands (usecompany fieldinstead). - CLI:
opportunity set-field,opportunity set-fields,opportunity unset-fieldcommands (useopportunity fieldinstead).
Fixed¶
- CLI: Help text formatting - added missing spaces in command examples (~78 instances).
- CLI: Improved
--cursorhelp text explaining incompatibility with--page-size. - CLI: Clarified
--csvhelp text to indicate it writes to file while stdout format is unchanged. - CLI: CommandContext validation and test isolation issues.
0.4.8 - 2025-12-31¶
Added¶
- CLI:
xaffinity field historyfor viewing field value change history. - CLI: Session caching for pipeline optimization via
AFFINITY_SESSION_CACHEenvironment variable. - CLI:
session start/end/statuscommands for managing session cache lifecycle. - CLI:
--session-cacheand--no-cacheglobal flags for cache control. - CLI: Cache hit/miss visibility with
--traceflag. - CLI:
config check-key --jsonnow includespatternfield showing key source. - SDK: Client-side filtering for list entries (V2 API does not support server-side filtering).
Changed¶
- CLI:
--filteron list entry commands now applies client-side with warning (V2 API limitation). - CLI: Removed
--opportunity-idfromlist entry add(opportunities are created atomically viaopportunity create --list-id).
Fixed¶
- SDK: Client-side filter parsing handles whitespace-only and unparseable filters gracefully.
- CLI:
--filteron list entries now returns proper field values (V2 API format).
0.4.0 - 2025-12-30¶
Added¶
- CLI:
config check-keycommand to check if an API key is configured (checks environment, .env, and config.toml). - CLI:
config setup-keycommand for secure API key configuration with hidden input, validation, and automatic .gitignore management. - CLI:
set-field,set-fields,unset-fieldcommands for person, company, opportunity, and list entry entities. - CLI:
list entry getcommand with field metadata display. - CLI: Enhanced
--expand-filtersyntax with OR (|), AND (&), NOT (!), NULL checks (=*,!=*), and contains (=~). - SDK:
list_entriesfield added toPersonmodel. - SDK: Unified filter parser with
parse()function andmatches()method for client-side filter evaluation.
Changed¶
- CLI: Authentication error hints now reference
config check-keyandconfig setup-keycommands. - CLI: Authentication documentation updated with Quick Setup section.
Fixed¶
- CLI: Default
--page-sizereduced from 200 to 100 to match Affinity API limit. - SDK: Async
merge()parameter names corrected (primaryCompanyId/duplicateCompanyId). - SDK: Cache invalidation added to async create/update/delete in
CompanyService.
Removed¶
- CLI: Deprecated
field-valueandfield-value-changescommand groups removed (use entity-specific field commands instead). - CLI: Deprecated
update-fieldandbatch-updatelist entry commands removed (useset-field/set-fieldsinstead).
0.3.0 - 2025-12-30¶
Added¶
- CLI:
xaffinity list export --expandfor exporting list entries with entity field expansion (company/person/opportunity fields). - CLI:
xaffinity field-value-changes lsfor viewing field value change history. - CLI:
xaffinity company get(id/URL/resolver selectors) with--all-fieldsand--expand lists|list-entries|people. - CLI:
xaffinity person get(id/URL/resolver selectors) with--all-fieldsand--expand lists|list-entries. - CLI:
xaffinity person lsandxaffinity company lswith search flags. - CLI:
xaffinity opportunitycommand group withls/get/create/update/delete. - CLI:
xaffinity note,xaffinity reminder, andxaffinity interactioncommand groups. - CLI:
xaffinity file uploadcommand for file uploads. - CLI: Write/merge/field operations for list entries.
- CLI:
--max-resultsand--allcontrols for pagination and expansions. - CLI: Progress reporting for all paginated commands.
- CLI: Rate limit visibility via SDK event hook.
- CLI:
--traceflag for debugging SDK requests. - SDK:
client.files.download_stream_with_info(...)exposes headers/filename/size alongside streamed bytes. - SDK: v1-only company association helpers
get_associated_person_ids(...)andget_associated_people(...). - SDK: List-scoped opportunity resolution helpers
resolve(...)andresolve_all(...). - SDK: Async parity for company and person services.
- SDK: Async parity for V1-only services.
- SDK: Async list and list entry write helpers.
- SDK: Pagination support for person resolution in
PersonServiceandAsyncPersonService. - SDK:
client.clear_cache()method for cache invalidation. - SDK: Field value changes service with
client.field_value_changes. - SDK: Detailed exception handling for
ConflictError,UnsafeUrlError, andUnsupportedOperationError. - SDK: Webhook
sent_attimestamp validation. - SDK: Request pipeline with policies (read-only mode, transport injection).
- SDK:
on_errorhook for error observability. - Inbound webhook parsing helpers:
parse_webhook(...),dispatch_webhook(...), andBodyRegistry. - Claude Code plugin for SDK/CLI documentation and guidance.
Changed¶
- CLI: Enum fields now display human-readable names instead of integers (type, status, direction, actionType).
- CLI: Datetimes render in local time with timezone info in column headers.
- CLI: Human/table output renders dict-shaped results as sections/tables (no JSON-looking panels).
- CLI:
--jsonoutput now uses section-keyeddataandmeta.pagination. - CLI: List-entry fields tables default to list-only fields; use
--list-entry-fields-scope allfor full payloads. - CLI: Domain columns are now linkified in table output.
- CLI: Output only pages when content would scroll.
FieldValueTypeis now V2-first and string-based (e.g.dropdown-multi,ranked-dropdown,interaction).ListEntry.entityis now discriminated byentity_type.- Rate limit API unified across sync and async clients.
Fixed¶
- SDK:
ListService.get()now uses V1 API to return correctlist_size. - CLI: JSON serialization now handles datetime objects correctly.
- Sync entity file download
deadline_secondshandling. - File downloads now use public services for company expansion pagination.
0.2.0 - 2025-12-17¶
Added¶
- Initial public release.
client.files.download_stream(...)andclient.files.download_to(...)for chunked file downloads.client.files.upload_path(...)andclient.files.upload_bytes(...)for ergonomic uploads.client.files.all(...)/client.files.iter(...)for auto-pagination over files.
Changed¶
- File downloads now follow redirects without forwarding credentials and use the standard retry/diagnostics policy.
client.files.list(...)andclient.files.upload(...)now require exactly one ofperson_id,organization_id, oropportunity_id(per API contract).