affinity-sdk 0.9.11__tar.gz → 0.9.14__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/workflows/ci.yml +10 -5
  2. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/workflows/release.yml +2 -2
  3. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.gitignore +1 -0
  4. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.pre-commit-config.yaml +7 -0
  5. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/CHANGELOG.md +43 -1
  6. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/PKG-INFO +3 -1
  7. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/README.md +2 -0
  8. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/company_cmds.py +4 -1
  9. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/list_cmds.py +4 -3
  10. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/person_cmds.py +4 -1
  11. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/task_cmds.py +22 -3
  12. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/interaction_utils.py +140 -20
  13. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/executor.py +522 -39
  14. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/models.py +16 -2
  15. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/output.py +213 -9
  16. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/schema.py +45 -3
  17. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/resolve.py +55 -1
  18. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/services/lists.py +55 -13
  19. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/cli/index.md +10 -0
  20. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/query-command.md +57 -1
  21. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/query-language.md +78 -0
  22. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/.claude-plugin/plugin.json +1 -1
  23. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/.claude-plugin/skills/affinity-mcp-workflows/SKILL.md +48 -23
  24. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/.claude-plugin/skills/query-language/SKILL.md +74 -2
  25. affinity_sdk-0.9.11/mcp/.registry/commands.json → affinity_sdk-0.9.14/mcp/.registry/commands.generated.json +439 -302
  26. affinity_sdk-0.9.14/mcp/.registry/mcp-commands.json +362 -0
  27. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/.registry/resource-templates.json +1 -1
  28. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/.registry/resources.json +2 -2
  29. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/CHANGELOG.md +71 -8
  30. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/COMPATIBILITY +1 -1
  31. affinity_sdk-0.9.14/mcp/VERSION +1 -0
  32. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/lib/cli-gateway.sh +38 -25
  33. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/lib/common.sh +3 -2
  34. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/mcp-bash.lock +2 -2
  35. affinity_sdk-0.9.14/mcp/mcpb.conf +49 -0
  36. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/data-model/data-model.md +29 -2
  37. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/server.d/env.sh +28 -1
  38. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/server.d/health-checks.sh +3 -3
  39. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/server.d/server.meta.json +1 -1
  40. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/tools/discover-commands/tool.sh +25 -3
  41. affinity_sdk-0.9.14/mcp/tools/execute-read-command/tool.meta.json +36 -0
  42. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/tools/execute-read-command/tool.sh +14 -12
  43. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/tools/execute-write-command/tool.meta.json +1 -1
  44. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/tools/execute-write-command/tool.sh +23 -25
  45. affinity_sdk-0.9.14/mcp/tools/query/tool.meta.json +104 -0
  46. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/tools/query/tool.sh +52 -8
  47. affinity_sdk-0.9.14/mcp/user-config.json +35 -0
  48. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/plugins/affinity-sdk/.claude-plugin/plugin.json +1 -1
  49. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/plugins/xaffinity-cli/.claude-plugin/plugin.json +1 -1
  50. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/pyproject.toml +1 -1
  51. affinity_sdk-0.9.14/tests/cli/test_query_list_entry_relationships.py +643 -0
  52. affinity_sdk-0.9.14/tests/cli/test_query_list_export_parity.py +319 -0
  53. affinity_sdk-0.9.14/tests/cli/test_query_output_flattening.py +668 -0
  54. affinity_sdk-0.9.14/tests/cli/test_query_unreplied_emails.py +251 -0
  55. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/integration/README.md +31 -0
  56. affinity_sdk-0.9.14/tests/integration/setup_query_parity_data.py +414 -0
  57. affinity_sdk-0.9.14/tests/integration/test_query_parity_integration.py +1124 -0
  58. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_interaction_utils.py +237 -0
  59. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_query_executor.py +203 -0
  60. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_query_expand_interactions.py +249 -0
  61. affinity_sdk-0.9.14/tools/generate_mcp_command_registry.py +239 -0
  62. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tools/sync_mcp_registry.py +2 -2
  63. affinity_sdk-0.9.11/mcp/.registry/commands-metadata.json +0 -114
  64. affinity_sdk-0.9.11/mcp/VERSION +0 -1
  65. affinity_sdk-0.9.11/mcp/mcpb.conf +0 -24
  66. affinity_sdk-0.9.11/mcp/tools/execute-read-command/tool.meta.json +0 -42
  67. affinity_sdk-0.9.11/mcp/tools/query/tool.meta.json +0 -105
  68. affinity_sdk-0.9.11/tools/generate_cli_commands_registry.py +0 -200
  69. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.claude-plugin/marketplace.json +0 -0
  70. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.editorconfig +0 -0
  71. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.env.example +0 -0
  72. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.gitattributes +0 -0
  73. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/CODEOWNERS +0 -0
  74. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  75. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  76. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/PULL_REQUEST_TEMPLATE/cli_command.md +0 -0
  77. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/PULL_REQUEST_TEMPLATE/release.md +0 -0
  78. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/dependabot.yml +0 -0
  79. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/labels.yml +0 -0
  80. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/pull_request_template.md +0 -0
  81. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/workflows/docs.yml +0 -0
  82. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/workflows/labels.yml +0 -0
  83. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/workflows/linkcheck.yml +0 -0
  84. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/workflows/openapi-validation.yml +0 -0
  85. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/workflows/plugin-build.yml +0 -0
  86. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/workflows/plugin-release.yml +0 -0
  87. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/workflows/release-detect.yml +0 -0
  88. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/.github/workflows/version-check.yml +0 -0
  89. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/CODE_OF_CONDUCT.md +0 -0
  90. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/CONTRIBUTING.md +0 -0
  91. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/LICENSE +0 -0
  92. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/SECURITY.md +0 -0
  93. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/VERSIONING.md +0 -0
  94. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/__init__.py +0 -0
  95. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/__init__.py +0 -0
  96. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/__main__.py +0 -0
  97. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/click_compat.py +0 -0
  98. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/__init__.py +0 -0
  99. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/_entity_files_dump.py +0 -0
  100. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/_list_entry_fields.py +0 -0
  101. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/_v1_parsing.py +0 -0
  102. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/completion_cmd.py +0 -0
  103. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/config_cmds.py +0 -0
  104. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/entry_cmds.py +0 -0
  105. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/field_cmds.py +0 -0
  106. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/interaction_cmds.py +0 -0
  107. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/note_cmds.py +0 -0
  108. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/opportunity_cmds.py +0 -0
  109. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/query_cmd.py +0 -0
  110. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/relationship_strength_cmds.py +0 -0
  111. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/reminder_cmds.py +0 -0
  112. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/resolve_url_cmd.py +0 -0
  113. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/session_cmds.py +0 -0
  114. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/version_cmd.py +0 -0
  115. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/commands/whoami_cmd.py +0 -0
  116. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/config.py +0 -0
  117. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/constants.py +0 -0
  118. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/context.py +0 -0
  119. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/csv_utils.py +0 -0
  120. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/date_utils.py +0 -0
  121. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/decorators.py +0 -0
  122. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/errors.py +0 -0
  123. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/field_utils.py +0 -0
  124. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/formatters.py +0 -0
  125. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/help_json.py +0 -0
  126. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/logging.py +0 -0
  127. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/main.py +0 -0
  128. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/options.py +0 -0
  129. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/paths.py +0 -0
  130. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/progress.py +0 -0
  131. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/__init__.py +0 -0
  132. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/aggregates.py +0 -0
  133. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/dates.py +0 -0
  134. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/exceptions.py +0 -0
  135. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/filters.py +0 -0
  136. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/parser.py +0 -0
  137. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/planner.py +0 -0
  138. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/query/progress.py +0 -0
  139. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/render.py +0 -0
  140. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/resolvers.py +0 -0
  141. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/results.py +0 -0
  142. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/runner.py +0 -0
  143. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/serialization.py +0 -0
  144. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/session_cache.py +0 -0
  145. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/cli/types.py +0 -0
  146. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/client.py +0 -0
  147. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/clients/__init__.py +0 -0
  148. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/clients/http.py +0 -0
  149. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/clients/pipeline.py +0 -0
  150. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/compare.py +0 -0
  151. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/downloads.py +0 -0
  152. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/exceptions.py +0 -0
  153. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/filters.py +0 -0
  154. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/hooks.py +0 -0
  155. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/inbound_webhooks.py +0 -0
  156. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/models/__init__.py +0 -0
  157. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/models/entities.py +0 -0
  158. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/models/pagination.py +0 -0
  159. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/models/rate_limit_snapshot.py +0 -0
  160. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/models/secondary.py +0 -0
  161. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/models/types.py +0 -0
  162. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/policies.py +0 -0
  163. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/progress.py +0 -0
  164. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/py.typed +0 -0
  165. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/services/__init__.py +0 -0
  166. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/services/companies.py +0 -0
  167. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/services/opportunities.py +0 -0
  168. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/services/persons.py +0 -0
  169. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/services/rate_limits.py +0 -0
  170. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/services/tasks.py +0 -0
  171. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/services/v1_only.py +0 -0
  172. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/affinity/types.py +0 -0
  173. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/codecov.yml +0 -0
  174. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/cli-development-guide.md +0 -0
  175. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/ai-integrations/index.md +0 -0
  176. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/changelog.md +0 -0
  177. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/cli/commands.md +0 -0
  178. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/cli/scripting.md +0 -0
  179. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/cli-reference.md +0 -0
  180. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/examples.md +0 -0
  181. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/getting-started.md +0 -0
  182. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/glossary.md +0 -0
  183. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/api-versions-and-routing.md +0 -0
  184. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/authentication.md +0 -0
  185. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/claude-code-plugins.md +0 -0
  186. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/configuration.md +0 -0
  187. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/csv-export.md +0 -0
  188. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/datetime-handling.md +0 -0
  189. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/debugging-hooks.md +0 -0
  190. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/errors-and-retries.md +0 -0
  191. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/field-types-and-values.md +0 -0
  192. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/field-values.md +0 -0
  193. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/filtering.md +0 -0
  194. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/ids-and-types.md +0 -0
  195. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/models.md +0 -0
  196. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/opportunity-associations.md +0 -0
  197. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/pagination.md +0 -0
  198. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/performance.md +0 -0
  199. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/rate-limits.md +0 -0
  200. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/sync-vs-async.md +0 -0
  201. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/guides/webhooks.md +0 -0
  202. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/index.md +0 -0
  203. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/mcp/index.md +0 -0
  204. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/client.md +0 -0
  205. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/exceptions.md +0 -0
  206. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/filters.md +0 -0
  207. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/models.md +0 -0
  208. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/companies.md +0 -0
  209. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/lists.md +0 -0
  210. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/opportunities.md +0 -0
  211. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/persons.md +0 -0
  212. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/tasks.md +0 -0
  213. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/v1/auth.md +0 -0
  214. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/v1/field-values.md +0 -0
  215. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/v1/fields.md +0 -0
  216. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/v1/files.md +0 -0
  217. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/v1/interactions.md +0 -0
  218. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/v1/notes.md +0 -0
  219. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/v1/relationships.md +0 -0
  220. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/v1/reminders.md +0 -0
  221. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/services/v1/webhooks.md +0 -0
  222. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/reference/types.md +0 -0
  223. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/docs/public/troubleshooting.md +0 -0
  224. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/examples/advanced_usage.py +0 -0
  225. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/examples/async_lifecycle.py +0 -0
  226. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/examples/basic_usage.py +0 -0
  227. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/examples/filter_builder.py +0 -0
  228. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/examples/hooks_debugging.py +0 -0
  229. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/examples/list_management.py +0 -0
  230. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/examples/resolve_helpers.py +0 -0
  231. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/examples/task_polling.py +0 -0
  232. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/.claude-plugin/.gitignore +0 -0
  233. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/.claude-plugin/.mcp.json +0 -0
  234. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/.claude-plugin/xaffinity-mcp.sh +0 -0
  235. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/.gitignore +0 -0
  236. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/.registry/prompts.json +0 -0
  237. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/Makefile +0 -0
  238. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/README.md +0 -0
  239. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/completions/entity-name.sh +0 -0
  240. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/completions/list-name.sh +0 -0
  241. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/completions/status-value.sh +0 -0
  242. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/docs/DEBUGGING.md +0 -0
  243. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/docs/internal/debugging-improvements-plan.md +0 -0
  244. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/docs/internal/mcp-bash-progress-timeout-feature-request.md +0 -0
  245. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/icon.svg +0 -0
  246. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/lib/cache.sh +0 -0
  247. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/lib/entity-types.sh +0 -0
  248. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/lib/field-resolution.sh +0 -0
  249. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/change-status/change-status.meta.json +0 -0
  250. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/change-status/change-status.txt +0 -0
  251. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/interaction-brief/interaction-brief.meta.json +0 -0
  252. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/interaction-brief/interaction-brief.txt +0 -0
  253. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/log-call/log-call.meta.json +0 -0
  254. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/log-call/log-call.txt +0 -0
  255. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/log-interaction-and-update-workflow/log-interaction-and-update-workflow.meta.json +0 -0
  256. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/log-interaction-and-update-workflow/log-interaction-and-update-workflow.txt +0 -0
  257. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/log-message/log-message.meta.json +0 -0
  258. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/log-message/log-message.txt +0 -0
  259. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/pipeline-review/pipeline-review.meta.json +0 -0
  260. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/pipeline-review/pipeline-review.txt +0 -0
  261. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/prepare-briefing/prepare-briefing.meta.json +0 -0
  262. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/prepare-briefing/prepare-briefing.txt +0 -0
  263. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/warm-intro/warm-intro.meta.json +0 -0
  264. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/prompts/warm-intro/warm-intro.txt +0 -0
  265. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/providers/xaffinity.sh +0 -0
  266. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/data-model/data-model.meta.json +0 -0
  267. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/field-catalogs/field-catalogs.meta.json +0 -0
  268. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/field-catalogs/field-catalogs.sh +0 -0
  269. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/interaction-enums/interaction-enums.json +0 -0
  270. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/interaction-enums/interaction-enums.meta.json +0 -0
  271. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/me/me.meta.json +0 -0
  272. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/me/me.sh +0 -0
  273. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/me-person-id/me-person-id.meta.json +0 -0
  274. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/me-person-id/me-person-id.sh +0 -0
  275. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/saved-views/saved-views.meta.json +0 -0
  276. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/saved-views/saved-views.sh +0 -0
  277. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/workflow-config/workflow-config.meta.json +0 -0
  278. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/resources/workflow-config/workflow-config.sh +0 -0
  279. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/scripts/install-framework.sh +0 -0
  280. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/server.d/policy.sh +0 -0
  281. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/server.d/requirements.json +0 -0
  282. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/test/README.md +0 -0
  283. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/test/run.sh +0 -0
  284. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/test-progress.sh +0 -0
  285. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/tools/discover-commands/tool.meta.json +0 -0
  286. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/tools/get-entity-dossier/tool.meta.json +0 -0
  287. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/tools/get-entity-dossier/tool.sh +0 -0
  288. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/tools/read-xaffinity-resource/tool.meta.json +0 -0
  289. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/tools/read-xaffinity-resource/tool.sh +0 -0
  290. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/xaffinity-mcp-env.sh +0 -0
  291. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mcp/xaffinity-mcp.sh +0 -0
  292. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/mkdocs.yml +0 -0
  293. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/plugins/affinity-sdk/.claude-plugin/skills/affinity-python-sdk/SKILL.md +0 -0
  294. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/plugins/xaffinity-cli/.claude-plugin/commands/affinity-help.md +0 -0
  295. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/plugins/xaffinity-cli/.claude-plugin/hooks/hooks.json +0 -0
  296. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/plugins/xaffinity-cli/.claude-plugin/hooks/pre-xaffinity.sh +0 -0
  297. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/plugins/xaffinity-cli/.claude-plugin/skills/xaffinity-cli-usage/SKILL.md +0 -0
  298. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/cli/test_formatters.py +0 -0
  299. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/cli/test_output_formats_integration.py +0 -0
  300. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/cli/test_query_output_truncation.py +0 -0
  301. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/conftest.py +0 -0
  302. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/fixtures/webhooks/field_value_updated.json +0 -0
  303. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/fixtures/webhooks/list_entry_created.json +0 -0
  304. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/fixtures/webhooks/organization_created.json +0 -0
  305. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/fixtures/webhooks/organization_merged.json +0 -0
  306. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/fixtures/webhooks/person_created.json +0 -0
  307. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/fixtures/webhooks/unknown_event.json +0 -0
  308. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/integration/conftest.py +0 -0
  309. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/integration/test_cli_writes.py +0 -0
  310. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/integration/test_sdk_reads.py +0 -0
  311. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/integration/test_sdk_writes.py +0 -0
  312. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/integration/test_sdk_writes_runner.py +0 -0
  313. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_affinity_client_wrapper_additional_coverage.py +0 -0
  314. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_async_file_download_streaming.py +0 -0
  315. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_column_limiting.py +0 -0
  316. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_command_context.py +0 -0
  317. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_company_cmds_coverage.py +0 -0
  318. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_company_get.py +0 -0
  319. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_config_cmds.py +0 -0
  320. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_csv_export.py +0 -0
  321. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_date_params.py +0 -0
  322. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_date_utils.py +0 -0
  323. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_datetime_formatting.py +0 -0
  324. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_datetime_header_offsets.py +0 -0
  325. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_domain_linkification.py +0 -0
  326. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_entity_files_dump.py +0 -0
  327. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_entry_field.py +0 -0
  328. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_error_rendering.py +0 -0
  329. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_field_history.py +0 -0
  330. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_field_value_humanization.py +0 -0
  331. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_get_interaction_dates.py +0 -0
  332. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_interaction_date_chunking.py +0 -0
  333. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_json_safety.py +0 -0
  334. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_list_cmds_coverage.py +0 -0
  335. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_list_expand_interactions.py +0 -0
  336. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_missing_coverage.py +0 -0
  337. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_no_network_commands.py +0 -0
  338. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_opportunity_cmds.py +0 -0
  339. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_opportunity_cmds_coverage.py +0 -0
  340. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_pager_logic.py +0 -0
  341. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_person_cmds_coverage.py +0 -0
  342. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_person_get.py +0 -0
  343. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_progress_manager.py +0 -0
  344. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_query_aggregates.py +0 -0
  345. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_query_cmd.py +0 -0
  346. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_query_dates.py +0 -0
  347. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_query_filters.py +0 -0
  348. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_query_integration.py +0 -0
  349. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_query_parser.py +0 -0
  350. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_query_planner.py +0 -0
  351. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_query_safety.py +0 -0
  352. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_query_schema.py +0 -0
  353. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_rate_limit_rendering.py +0 -0
  354. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_resolve_url_parsing.py +0 -0
  355. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_result_summary.py +0 -0
  356. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_serialization.py +0 -0
  357. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_session_cache.py +0 -0
  358. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_types.py +0 -0
  359. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_unreplied_email.py +0 -0
  360. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_v1_only_cmds.py +0 -0
  361. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_v1_parsing.py +0 -0
  362. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_cli_write_ops.py +0 -0
  363. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_client.py +0 -0
  364. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_compare.py +0 -0
  365. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_env_helpers.py +0 -0
  366. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_exceptions_coverage.py +0 -0
  367. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_field_value_type.py +0 -0
  368. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_file_download_streaming.py +0 -0
  369. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_file_upload_helpers_and_validation.py +0 -0
  370. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_filters.py +0 -0
  371. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_hooks.py +0 -0
  372. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_http_client_additional_coverage.py +0 -0
  373. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_http_client_remaining_coverage.py +0 -0
  374. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_inbound_webhooks.py +0 -0
  375. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_integration_smoke.py +0 -0
  376. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_interactions_and_files_regressions.py +0 -0
  377. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_isodatetime.py +0 -0
  378. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_list_entry_membership_and_opportunities.py +0 -0
  379. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_models.py +0 -0
  380. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_new_features.py +0 -0
  381. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_pagination_iterators.py +0 -0
  382. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_public_api.py +0 -0
  383. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_raw_pipeline_hook_events.py +0 -0
  384. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_requirements_additional.py +0 -0
  385. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_sdk_interaction_dates.py +0 -0
  386. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_service_get_parameter_combinations.py +0 -0
  387. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_services_companies_coverage_gaps.py +0 -0
  388. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_services_lists_additional_coverage.py +0 -0
  389. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_services_lists_coverage_gaps.py +0 -0
  390. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_services_opportunities_additional_coverage.py +0 -0
  391. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_services_opportunities_coverage_gaps.py +0 -0
  392. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_services_opportunities_tasks_additional_coverage.py +0 -0
  393. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_services_persons_companies_additional_coverage.py +0 -0
  394. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_services_rate_limits_coverage_gaps.py +0 -0
  395. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_services_v1_only_async.py +0 -0
  396. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_services_v1_only_coverage_gaps.py +0 -0
  397. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_small_modules_coverage.py +0 -0
  398. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_transport_and_readonly_mode.py +0 -0
  399. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_v1_only_services_additional_coverage.py +0 -0
  400. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tests/test_v1_only_services_more_coverage.py +0 -0
  401. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tools/check_cli_patterns.py +0 -0
  402. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tools/generate_requirements_mapping.py +0 -0
  403. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tools/sync_mcp_version.py +0 -0
  404. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tools/sync_plugin_version.py +0 -0
  405. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/tools/validate_openapi_models.py +0 -0
  406. {affinity_sdk-0.9.11 → affinity_sdk-0.9.14}/uv.lock +0 -0
@@ -84,14 +84,19 @@ jobs:
84
84
  echo "Skipping traceability mapping (docs/internal not present in repo checkout)."
85
85
  fi
86
86
 
87
- - name: CLI commands registry is valid
87
+ - name: MCP commands registry is valid
88
88
  run: |
89
89
  # Verify registry file exists and is valid JSON with expected structure
90
90
  python -c "
91
91
  import json
92
92
  import sys
93
- with open('mcp/.registry/commands.json') as f:
93
+ with open('mcp/.registry/commands.generated.json') as f:
94
94
  data = json.load(f)
95
+ # Check _generated metadata (auto-generated indicator)
96
+ assert '_generated' in data, 'Missing _generated metadata'
97
+ assert 'generator' in data['_generated'], 'Missing generator in _generated'
98
+ assert 'cliVersion' in data['_generated'], 'Missing cliVersion in _generated'
99
+ # Check commands structure
95
100
  assert 'commands' in data, 'Missing commands key'
96
101
  assert isinstance(data['commands'], list), 'commands must be a list'
97
102
  assert len(data['commands']) > 0, 'commands list is empty'
@@ -99,11 +104,11 @@ jobs:
99
104
  assert 'name' in cmd, f'Command missing name: {cmd}'
100
105
  assert 'category' in cmd, f'Command {cmd[\"name\"]} missing category'
101
106
  assert cmd['category'] in ('read', 'write', 'local'), f'Invalid category for {cmd[\"name\"]}'
102
- print(f'Registry valid: {len(data[\"commands\"])} commands')
107
+ print(f'Registry valid: {len(data[\"commands\"])} commands (v{data[\"_generated\"][\"cliVersion\"]})')
103
108
  "
104
109
  # Regenerate registry and verify it matches committed version
105
- python tools/generate_cli_commands_registry.py
106
- git diff --exit-code mcp/.registry/commands.json
110
+ python tools/generate_mcp_command_registry.py
111
+ git diff --exit-code mcp/.registry/commands.generated.json
107
112
 
108
113
  test-timezone:
109
114
  runs-on: ubuntu-latest
@@ -159,8 +159,8 @@ jobs:
159
159
 
160
160
  - name: Verify registry is up to date
161
161
  run: |
162
- python tools/generate_cli_commands_registry.py
163
- git diff --exit-code mcp/.registry/commands.json
162
+ python tools/generate_mcp_command_registry.py
163
+ git diff --exit-code mcp/.registry/commands.generated.json
164
164
 
165
165
  build:
166
166
  runs-on: ubuntu-latest
@@ -59,3 +59,4 @@ tmp-scripts/set_primary_email_status.py
59
59
  test-output/
60
60
  CLAUDE.md
61
61
  .sandbox.env
62
+ tests/integration/query_parity_test_data.json
@@ -66,3 +66,10 @@ repos:
66
66
  language: python
67
67
  files: ^(mcp/VERSION|mcp/\.claude-plugin/plugin\.json|mcp/server\.d/server\.meta\.json)$
68
68
  pass_filenames: false
69
+ - id: pytest-pre-push
70
+ name: Run tests before push
71
+ entry: pytest tests/ -x -q --tb=short
72
+ language: system
73
+ pass_filenames: false
74
+ stages: [pre-push]
75
+ always_run: true
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## 0.9.14 - 2026-01-19
11
+
12
+ ### Fixed
13
+ - CLI: Query `select` clause now automatically includes `expand` fields. Previously, using `select` with `expand` would filter out the expansion data (e.g., `interactionDates`, `unrepliedEmails`), requiring users to explicitly list expansions in `select`.
14
+
15
+ ## 0.9.13 - 2026-01-19
16
+
17
+ ### Added
18
+ - CLI: Query `include` for `listEntries` now supports `persons`, `companies`, `opportunities`, and `interactions`. Fetches related entities based on list entry entity type (e.g., company entries get associated persons).
19
+ - CLI: Query `expand: ["unrepliedEmails"]` now works with `listEntries`. Checks each list entry's underlying entity for unreplied incoming emails.
20
+ - CLI: Query extended include syntax with parameters:
21
+ - `{include: {interactions: {limit: 50, days: 180}}}` - Limit and lookback control
22
+ - `{include: {opportunities: {list: "Pipeline"}}}` - Scope to specific opportunity list
23
+ - `{include: {persons: {where: {...}}}}` - Filter included entities
24
+
25
+ ### Fixed
26
+ - CLI: Query TOON format now correctly flattens `fields.*` and `interactionDates` like 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.
27
+ - CLI: `list export --check-unreplied-emails` now works standalone without requiring `--expand`.
28
+ - CLI: `expand: ["interactionDates"]` now always produces 8 canonical columns regardless of data presence, ensuring consistent schema across all records.
29
+ - 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.
30
+
31
+ ### Performance
32
+ - 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 via `XAFFINITY_QUERY_CONCURRENCY`).
33
+
34
+ ## 0.9.12 - 2026-01-17
35
+
36
+ ### Changed
37
+ - CLI: Query `listEntries` now normalizes reference field values to display strings:
38
+ - Person fields: `{"firstName": "Jane", "lastName": "Doe"}` → `"Jane Doe"`
39
+ - Company fields: `{"name": "Acme Corp", "id": 123}` → `"Acme Corp"`
40
+ - Multi-person/company fields: Arrays of names instead of raw objects
41
+ - Use `expand` or `include` for full entity data when needed
42
+ - CLI: Query output with explicit `select` on `fields.*` paths now flattens fields to top-level columns in table/CSV/markdown formats. JSON output preserves nested structure.
43
+
10
44
  ## 0.9.11 - 2026-01-17
11
45
 
12
46
  ### Added
@@ -207,7 +241,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
207
241
  - CLI: `--limit` alias for `--max-results` on all `ls` commands (LLM-friendly).
208
242
  - CLI: Option aliases now included in `--help --json` output.
209
243
 
210
- ## 0.8.0 - Unreleased
244
+ ## 0.8.2 - 2026-01-10
245
+
246
+ _No user-facing changes. Version bump for PyPI release._
247
+
248
+ ## 0.8.1 - 2026-01-10
249
+
250
+ _No user-facing changes. Version bump for PyPI release._
251
+
252
+ ## 0.8.0 - 2026-01-10
211
253
 
212
254
  ### Changed
213
255
  - CLI: Renamed `--json` to `--set-json` on `person field`, `company field`, `opportunity field` commands to avoid conflict with global `--json` output flag.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: affinity-sdk
3
- Version: 0.9.11
3
+ Version: 0.9.14
4
4
  Summary: A modern, strongly-typed Python SDK for the Affinity CRM API
5
5
  Project-URL: Homepage, https://github.com/yaniv-golan/affinity-sdk
6
6
  Project-URL: Documentation, https://yaniv-golan.github.io/affinity-sdk/latest/
@@ -126,6 +126,8 @@ Optional: install the CLI:
126
126
  pipx install "affinity-sdk[cli]"
127
127
  ```
128
128
 
129
+ The CLI includes a powerful `query` command for structured data extraction with filtering, aggregations, and relationship includes. Output formats include JSON, CSV, markdown, and TOON (token-optimized for LLMs).
130
+
129
131
  CLI docs: https://yaniv-golan.github.io/affinity-sdk/latest/cli/
130
132
 
131
133
  ### MCP Server
@@ -69,6 +69,8 @@ Optional: install the CLI:
69
69
  pipx install "affinity-sdk[cli]"
70
70
  ```
71
71
 
72
+ The CLI includes a powerful `query` command for structured data extraction with filtering, aggregations, and relationship includes. Output formats include JSON, CSV, markdown, and TOON (token-optimized for LLMs).
73
+
72
74
  CLI docs: https://yaniv-golan.github.io/affinity-sdk/latest/cli/
73
75
 
74
76
  ### MCP Server
@@ -1904,7 +1904,10 @@ def company_merge(
1904
1904
  primary_id: int,
1905
1905
  duplicate_id: int,
1906
1906
  ) -> None:
1907
- """Merge a duplicate company into a primary company (beta)."""
1907
+ """Merge a duplicate company into a primary (beta).
1908
+
1909
+ Returns a taskUrl for tracking progress. Use 'task wait <url>' to wait for completion.
1910
+ """
1908
1911
 
1909
1912
  def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
1910
1913
  client = ctx.get_client(warnings=warnings)
@@ -1055,8 +1055,8 @@ def list_export(
1055
1055
  ):
1056
1056
  next_cursor = page_next_cursor
1057
1057
 
1058
- if not want_expand:
1059
- # No expansion - yield row as-is
1058
+ if not want_expand and not check_unreplied_emails:
1059
+ # No expansion and no unreplied email check - yield row as-is
1060
1060
  rows_written += 1
1061
1061
  if progress is not None and task_id is not None:
1062
1062
  filter_desc = _format_filter_progress(csv_iter_state)
@@ -1469,7 +1469,8 @@ def list_export(
1469
1469
  ):
1470
1470
  next_cursor = page_next_cursor
1471
1471
 
1472
- if not want_expand:
1472
+ if not want_expand and not check_unreplied_emails:
1473
+ # No expansion and no unreplied email check - add row as-is
1473
1474
  rows.append(row)
1474
1475
  if progress is not None and task_id is not None:
1475
1476
  filter_desc = _format_filter_progress(table_iter_state)
@@ -1763,7 +1763,10 @@ def person_merge(
1763
1763
  primary_id: int,
1764
1764
  duplicate_id: int,
1765
1765
  ) -> None:
1766
- """Merge a duplicate person into a primary person (beta)."""
1766
+ """Merge a duplicate person into a primary (beta).
1767
+
1768
+ Returns a taskUrl for tracking progress. Use 'task wait <url>' to wait for completion.
1769
+ """
1767
1770
 
1768
1771
  def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
1769
1772
  client = ctx.get_client(warnings=warnings)
@@ -13,7 +13,19 @@ from ..serialization import serialize_model_for_cli
13
13
 
14
14
  @click.group(name="task", cls=RichGroup)
15
15
  def task_group() -> None:
16
- """Task commands."""
16
+ """Poll async tasks (e.g., merges).
17
+
18
+ Some operations like 'company merge' and 'person merge' run asynchronously
19
+ and return a task URL. Use these commands to check status or wait for completion.
20
+
21
+ Example workflow:
22
+
23
+ xaffinity company merge 123 456 --json
24
+
25
+ # Returns {"taskUrl": "https://api.affinity.co/v2/tasks/..."}
26
+
27
+ xaffinity task wait "https://api.affinity.co/v2/tasks/..."
28
+ """
17
29
 
18
30
 
19
31
  def _task_payload(task: MergeTask) -> dict[str, object]:
@@ -26,7 +38,10 @@ def _task_payload(task: MergeTask) -> dict[str, object]:
26
38
  @output_options
27
39
  @click.pass_obj
28
40
  def task_get(ctx: CLIContext, task_url: str) -> None:
29
- """Get task status."""
41
+ """Get current status of an async task.
42
+
43
+ Returns task status (pending, in_progress, success, failed) without waiting.
44
+ """
30
45
 
31
46
  def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
32
47
  client = ctx.get_client(warnings=warnings)
@@ -78,7 +93,11 @@ def task_wait(
78
93
  poll_interval: float,
79
94
  max_poll_interval: float,
80
95
  ) -> None:
81
- """Wait for a task to complete."""
96
+ """Wait for an async task to complete.
97
+
98
+ Polls the task URL with exponential backoff until it reaches 'success' or 'failed'.
99
+ Returns the final task status. Raises an error if the task fails or times out.
100
+ """
82
101
 
83
102
  def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
84
103
  client = ctx.get_client(warnings=warnings)
@@ -10,6 +10,7 @@ This module provides:
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import asyncio
13
14
  import logging
14
15
  from datetime import datetime, timedelta, timezone
15
16
  from typing import TYPE_CHECKING, Any
@@ -270,13 +271,21 @@ async def _resolve_person_names_async(
270
271
  client: AsyncAffinity,
271
272
  person_ids: list[int],
272
273
  cache: dict[int, str] | None = None,
274
+ *,
275
+ person_semaphore: asyncio.Semaphore | None = None,
273
276
  ) -> list[str]:
274
277
  """Resolve person IDs to names asynchronously, using cache when available.
275
278
 
279
+ Person fetches run in parallel with bounded concurrency via a SHARED semaphore.
280
+
276
281
  Args:
277
282
  client: AsyncAffinity client for async API calls
278
283
  person_ids: List of person IDs to resolve
279
284
  cache: Optional dict cache (mutated in place). Thread-safe under CPython GIL.
285
+ person_semaphore: Optional SHARED semaphore for bounded concurrent fetches.
286
+ IMPORTANT: Pass the same semaphore across all calls to limit total
287
+ concurrent person API calls. Creating a new semaphore per call defeats
288
+ the bounded concurrency purpose.
280
289
 
281
290
  Returns:
282
291
  List of person names in same order as input IDs.
@@ -285,42 +294,56 @@ async def _resolve_person_names_async(
285
294
  if cache is None:
286
295
  cache = {}
287
296
 
288
- names: list[str] = []
289
- for pid in person_ids:
290
- if pid in cache:
291
- names.append(cache[pid])
292
- continue
297
+ # NOTE: Benign race possible - two tasks may both see same ID as uncached
298
+ # before either updates cache. Result: duplicate fetch, correct final state.
299
+ uncached_ids = [pid for pid in person_ids if pid not in cache]
293
300
 
294
- try:
295
- # Fetch person name from API asynchronously
296
- from affinity.types import PersonId
301
+ if uncached_ids:
302
+ # Use SHARED semaphore from caller, or create local fallback (for backwards compat)
303
+ sem = person_semaphore or asyncio.Semaphore(10)
297
304
 
298
- person = await client.persons.get(PersonId(pid))
299
- name = person.full_name or f"Person {pid}"
300
- cache[pid] = name
301
- names.append(name)
302
- except Exception:
303
- # Cache as fallback to avoid repeated failures
304
- cache[pid] = f"Person {pid}"
305
- names.append(f"Person {pid}")
305
+ async def fetch_person(pid: int) -> None:
306
+ async with sem:
307
+ try:
308
+ from affinity.types import PersonId
306
309
 
307
- return names
310
+ person = await client.persons.get(PersonId(pid))
311
+ name = person.full_name or f"Person {pid}"
312
+ cache[pid] = name
313
+ except Exception:
314
+ # Cache as fallback to avoid repeated failures
315
+ cache[pid] = f"Person {pid}"
316
+
317
+ # PERF: Parallelize person fetches with bounded concurrency
318
+ await asyncio.gather(*[fetch_person(pid) for pid in uncached_ids])
319
+
320
+ # Return names in original order
321
+ return [cache.get(pid, f"Person {pid}") for pid in person_ids]
308
322
 
309
323
 
324
+ # PERF: section_iteration_boundary
310
325
  async def resolve_interaction_names_async(
311
326
  client: AsyncAffinity,
312
327
  interaction_data: dict[str, Any] | None,
313
328
  cache: dict[int, str] | None = None,
329
+ *,
330
+ person_semaphore: asyncio.Semaphore | None = None,
314
331
  ) -> None:
315
332
  """Resolve teamMemberNames in transformed interaction data asynchronously.
316
333
 
317
334
  Mutates the interaction_data dict in place, adding teamMemberNames
318
335
  to any section that has teamMemberIds.
319
336
 
337
+ Sections (lastMeeting, nextMeeting, lastEmail) are resolved in parallel.
338
+ Person fetches within each section use a SHARED semaphore for bounded concurrency.
339
+
320
340
  Args:
321
341
  client: AsyncAffinity client for async API calls
322
342
  interaction_data: Transformed interaction data from transform_interaction_data()
323
343
  cache: Optional dict cache for person names (mutated in place)
344
+ person_semaphore: Optional SHARED semaphore for bounded person resolution.
345
+ If not provided, a local semaphore is created (not recommended for
346
+ multi-record expansion - pass shared semaphore from caller).
324
347
  """
325
348
  if interaction_data is None:
326
349
  return
@@ -328,16 +351,21 @@ async def resolve_interaction_names_async(
328
351
  if cache is None:
329
352
  cache = {}
330
353
 
331
- # Process each section that may have teamMemberIds
332
- for section_key in ("lastMeeting", "nextMeeting", "lastEmail"):
354
+ async def resolve_section(section_key: str) -> None:
355
+ """Resolve person names for a single section."""
333
356
  section = interaction_data.get(section_key)
334
357
  if section and "teamMemberIds" in section:
335
358
  person_ids = section["teamMemberIds"]
336
359
  if person_ids:
337
360
  section["teamMemberNames"] = await _resolve_person_names_async(
338
- client, person_ids, cache
361
+ client, person_ids, cache, person_semaphore=person_semaphore
339
362
  )
340
363
 
364
+ # PERF: Parallelize over sections (lastMeeting, nextMeeting, lastEmail)
365
+ await asyncio.gather(
366
+ *[resolve_section(key) for key in ("lastMeeting", "nextMeeting", "lastEmail")]
367
+ )
368
+
341
369
 
342
370
  # =============================================================================
343
371
  # Unreplied Email Detection
@@ -436,6 +464,98 @@ def check_unreplied_email(
436
464
  return None
437
465
 
438
466
 
467
+ async def async_check_unreplied_email(
468
+ client: AsyncAffinity,
469
+ entity_type: str | int,
470
+ entity_id: int,
471
+ lookback_days: int = 30,
472
+ ) -> dict[str, Any] | None:
473
+ """Async version: Check for unreplied incoming emails for an entity.
474
+
475
+ Supports person, company, and opportunity entity types.
476
+ Also handles V1 integer entityType formats (0=person, 1=company).
477
+
478
+ Args:
479
+ client: AsyncAffinity client for API calls
480
+ entity_type: "company", "person", "opportunity" (or V1 integers 0, 1)
481
+ entity_id: The entity ID
482
+ lookback_days: Number of days to look back for emails (default 30)
483
+
484
+ Returns:
485
+ Dict with unreplied email info if found, None otherwise.
486
+ Example: {
487
+ "date": "2026-01-10T10:00:00Z",
488
+ "daysSince": 5,
489
+ "subject": "Following up on our conversation",
490
+ }
491
+ """
492
+ from affinity.models.types import InteractionDirection, InteractionType
493
+ from affinity.types import CompanyId, OpportunityId, PersonId
494
+
495
+ try:
496
+ now = datetime.now(timezone.utc)
497
+ start_time = now - timedelta(days=lookback_days)
498
+
499
+ # Build entity-specific filter kwargs
500
+ # Handle V1 (integer) and V2 (string) entityType formats
501
+ iter_kwargs: dict[str, Any] = {
502
+ "type": InteractionType.EMAIL,
503
+ "start_time": start_time,
504
+ "end_time": now,
505
+ }
506
+
507
+ if entity_type in ("company", 1, "organization"):
508
+ iter_kwargs["company_id"] = CompanyId(entity_id)
509
+ elif entity_type in ("person", 0):
510
+ iter_kwargs["person_id"] = PersonId(entity_id)
511
+ elif entity_type == "opportunity":
512
+ iter_kwargs["opportunity_id"] = OpportunityId(entity_id)
513
+ else:
514
+ logger.debug(f"Unsupported entity type for unreplied email check: {entity_type}")
515
+ return None
516
+
517
+ # Fetch email interactions for the entity
518
+ emails = []
519
+ async for email in client.interactions.iter(**iter_kwargs):
520
+ emails.append(email)
521
+
522
+ if not emails:
523
+ return None
524
+
525
+ # Sort by date descending (most recent first)
526
+ emails.sort(key=lambda e: e.date, reverse=True)
527
+
528
+ # Find the most recent incoming email
529
+ last_incoming = None
530
+ for email in emails:
531
+ if email.direction == InteractionDirection.INCOMING:
532
+ last_incoming = email
533
+ break
534
+
535
+ if not last_incoming:
536
+ return None
537
+
538
+ # Check if there's an outgoing email after the last incoming
539
+ has_reply = any(
540
+ e.direction == InteractionDirection.OUTGOING and e.date > last_incoming.date
541
+ for e in emails
542
+ )
543
+
544
+ if has_reply:
545
+ return None
546
+
547
+ # Return unreplied email info
548
+ return {
549
+ "date": _format_datetime(last_incoming.date),
550
+ "daysSince": _days_since(last_incoming.date, now),
551
+ "subject": last_incoming.subject,
552
+ }
553
+
554
+ except Exception as e:
555
+ logger.warning(f"Failed to check unreplied emails for {entity_type} {entity_id}: {e}")
556
+ return None
557
+
558
+
439
559
  def flatten_unreplied_email_for_csv(unreplied: dict[str, Any] | None) -> dict[str, str]:
440
560
  """Flatten unreplied email data for CSV columns.
441
561