Highlights
Retry-safety overhaul for field --set and note create. Eliminates two retry hazards surfaced by a recent agent-driven intake incident:
- The four entity-field commands (
entry/company/person/opportunity field --set) now pre-validate every value up front. A bad person id, unknown dropdown option, or malformed datetime aborts with exit 2 before any API write — instead of partial-committing the earlier--setvalues when a later one fails. Repeated--setof an unchanged value now also short-circuits both theDELETEand thePOST, so agent retries no longer pollute the audit log withremove + addof the same value. note createemits a stderr warning when a content-identical note from the same author arrived within the last 5 minutes (creator scope resolved via cached/auth/whoami, rate-limit-exempt). The create itself still proceeds — informational only, with--skip-duplicate-checkand--duplicate-window-secondsescape hatches.
Also fixes a latent bug where opportunity field --set on enriched fields was silently broken (V1 numeric id never resolved).
Breaking Changes
- CLI:
entry/company/person/opportunity field --setnow performs upfront client-side validation. Failures the SDK can detect locally (invalid person/company id, unknown dropdown option, malformed datetime, etc.) raise a single aggregatedCLIError(exit 2) before any API write. Previously, partial--setsequences would commit earlier values before a later one failed.- Migration: No code change needed for valid payloads. For agents that previously caught exit 2 and retried, the retry is now safe — no prior writes to undo. Server-side failures (HTTP 400 mid-sequence) may still leave partial state because Affinity has no transactions; see
test_entry_field_partial_failureintests/test_cli_entry_field.pyfor that scenario.
- Migration: No code change needed for valid payloads. For agents that previously caught exit 2 and retried, the retry is now safe — no prior writes to undo. Server-side failures (HTTP 400 mid-sequence) may still leave partial state because Affinity has no transactions; see
What's New
- CLI: No-op short-circuit on
field --set— when the new value already matches the existing value (dropdown id, person id, number, datetime, text), skip both theDELETEand thePOST. Multi-value--setstill writes whenever the new set differs from the existing set (subset is NOT a no-op — set semantics is REPLACE). Per-type comparator rules inaffinity/cli/field_utils.py::value_equals_existing. - CLI:
note create --skip-duplicate-checkand--duplicate-window-seconds Nflags. Default behavior emits a stderr warning when a content-identical note by the same author was created within the last N seconds (default 300). Useful safety net for AI agents that re-issue create after a parse failure on an earlier--json-less invocation. - CLI: Type-aware hint on
_coerce_entity_iderrors. Passing--set Owner "<full name>"now points the user atxaffinity person ls --json --query "<name>"to look up the numeric id, instead of bouncing the request to the server. - CLI:
--outputhelp text corrected — defaults totableregardless of pipe state. Pass--jsonexplicitly when piping to a JSON parser. - SDK: New shared helpers in
affinity.cli.field_utils:pre_validate_set_operations,value_equals_existing,execute_v1_set_phase,execute_v2_set_phase,execute_append_phase. The four entity-field commands now route through these helpers for consistent validate/no-op/write semantics.
Bug Fixes
opportunity field --seton enriched fields (e.g.--set "Source of Introduction" "...") now resolves to V1 numeric ids correctly. Previous code passed the enriched V2 literal directly toFieldValueCreate, breaking enriched-field writes on opportunities. Discovered during the migration to the shared set-phase helpers.entry field --appendno longer captures a stale existing-values snapshot between the set and append phases — the helper refresh handoff is explicit.
Plugin / MCP updates (shipped alongside)
xaffinity-cliplugin: 1.8.2 → 1.9.0 — two new pitfalls added to thexaffinity-cli-usageskill: (1) side-effecting commands must capture-then-parse (out=$(xaffinity ...)) rather thanxaffinity ... | python3 -c "json.loads(...)", because pipeline exit codes drop the side-effect signal; (2) person/company field values require numeric IDs — the CLI now aborts cleanly on names with a hint at the lookup query.xaffinity-mcpserver: 1.22.1 → 1.22.2 — minimum CLI bumped to 1.15.0 (mcp/COMPATIBILITY) so the strict-by-default validation, no-op short-circuit, and duplicate-note warning are present for agents driving the MCP gateway. Seemcp/CHANGELOG.mdfor details.
Quick Install
SDK only:
pip install affinity-sdkSDK + CLI:
pipx install "affinity-sdk[cli]"MCP Server for Claude Desktop (easiest - MCPB bundle):
- Install CLI:
pipx install "affinity-sdk[cli]" - (Optional) Pre-configure API key:
xaffinity config setup-key- If skipped, Claude Desktop will prompt for your API key during install
- Install xaffinity MCP in Claude Desktop (download and double-click)
Other MCP clients (Cursor, Windsurf, VS Code, etc.) require manual configuration - see MCP docs.