affinity-sdk 0.4.7__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 (219) hide show
  1. affinity_sdk-0.4.7/.claude-plugin/commands/affinity-help.md +58 -0
  2. affinity_sdk-0.4.7/.claude-plugin/hooks/hooks.json +17 -0
  3. affinity_sdk-0.4.7/.claude-plugin/hooks/pre-xaffinity.sh +36 -0
  4. affinity_sdk-0.4.7/.claude-plugin/marketplace.json +16 -0
  5. affinity_sdk-0.4.7/.claude-plugin/plugin.json +27 -0
  6. affinity_sdk-0.4.7/.claude-plugin/skills/querying-affinity-crm/CLI_REFERENCE.md +349 -0
  7. affinity_sdk-0.4.7/.claude-plugin/skills/querying-affinity-crm/LIST_EXPORT_EXPAND.md +177 -0
  8. affinity_sdk-0.4.7/.claude-plugin/skills/querying-affinity-crm/SDK_REFERENCE.md +259 -0
  9. affinity_sdk-0.4.7/.claude-plugin/skills/querying-affinity-crm/SKILL.md +213 -0
  10. affinity_sdk-0.4.7/.editorconfig +19 -0
  11. affinity_sdk-0.4.7/.env.example +3 -0
  12. affinity_sdk-0.4.7/.gitattributes +7 -0
  13. affinity_sdk-0.4.7/.github/CODEOWNERS +9 -0
  14. affinity_sdk-0.4.7/.github/ISSUE_TEMPLATE/bug_report.yml +44 -0
  15. affinity_sdk-0.4.7/.github/ISSUE_TEMPLATE/feature_request.yml +27 -0
  16. affinity_sdk-0.4.7/.github/PULL_REQUEST_TEMPLATE/cli_command.md +89 -0
  17. affinity_sdk-0.4.7/.github/dependabot.yml +12 -0
  18. affinity_sdk-0.4.7/.github/labels.yml +51 -0
  19. affinity_sdk-0.4.7/.github/pull_request_template.md +14 -0
  20. affinity_sdk-0.4.7/.github/workflows/ci.yml +56 -0
  21. affinity_sdk-0.4.7/.github/workflows/docs.yml +97 -0
  22. affinity_sdk-0.4.7/.github/workflows/labels.yml +22 -0
  23. affinity_sdk-0.4.7/.github/workflows/linkcheck.yml +44 -0
  24. affinity_sdk-0.4.7/.github/workflows/openapi-validation.yml +30 -0
  25. affinity_sdk-0.4.7/.github/workflows/release.yml +178 -0
  26. affinity_sdk-0.4.7/.gitignore +58 -0
  27. affinity_sdk-0.4.7/.pre-commit-config.yaml +55 -0
  28. affinity_sdk-0.4.7/CHANGELOG.md +94 -0
  29. affinity_sdk-0.4.7/CODE_OF_CONDUCT.md +96 -0
  30. affinity_sdk-0.4.7/CONTRIBUTING.md +91 -0
  31. affinity_sdk-0.4.7/LICENSE +21 -0
  32. affinity_sdk-0.4.7/PKG-INFO +584 -0
  33. affinity_sdk-0.4.7/README.md +527 -0
  34. affinity_sdk-0.4.7/SECURITY.md +10 -0
  35. affinity_sdk-0.4.7/affinity/__init__.py +137 -0
  36. affinity_sdk-0.4.7/affinity/cli/__init__.py +7 -0
  37. affinity_sdk-0.4.7/affinity/cli/click_compat.py +27 -0
  38. affinity_sdk-0.4.7/affinity/cli/commands/__init__.py +1 -0
  39. affinity_sdk-0.4.7/affinity/cli/commands/_entity_files_dump.py +216 -0
  40. affinity_sdk-0.4.7/affinity/cli/commands/_list_entry_fields.py +41 -0
  41. affinity_sdk-0.4.7/affinity/cli/commands/_v1_parsing.py +52 -0
  42. affinity_sdk-0.4.7/affinity/cli/commands/company_cmds.py +2100 -0
  43. affinity_sdk-0.4.7/affinity/cli/commands/completion_cmd.py +31 -0
  44. affinity_sdk-0.4.7/affinity/cli/commands/config_cmds.py +505 -0
  45. affinity_sdk-0.4.7/affinity/cli/commands/field_cmds.py +135 -0
  46. affinity_sdk-0.4.7/affinity/cli/commands/interaction_cmds.py +384 -0
  47. affinity_sdk-0.4.7/affinity/cli/commands/list_cmds.py +2732 -0
  48. affinity_sdk-0.4.7/affinity/cli/commands/note_cmds.py +316 -0
  49. affinity_sdk-0.4.7/affinity/cli/commands/opportunity_cmds.py +1145 -0
  50. affinity_sdk-0.4.7/affinity/cli/commands/person_cmds.py +1931 -0
  51. affinity_sdk-0.4.7/affinity/cli/commands/relationship_strength_cmds.py +44 -0
  52. affinity_sdk-0.4.7/affinity/cli/commands/reminder_cmds.py +450 -0
  53. affinity_sdk-0.4.7/affinity/cli/commands/resolve_url_cmd.py +125 -0
  54. affinity_sdk-0.4.7/affinity/cli/commands/task_cmds.py +83 -0
  55. affinity_sdk-0.4.7/affinity/cli/commands/version_cmd.py +27 -0
  56. affinity_sdk-0.4.7/affinity/cli/commands/whoami_cmd.py +25 -0
  57. affinity_sdk-0.4.7/affinity/cli/config.py +108 -0
  58. affinity_sdk-0.4.7/affinity/cli/context.py +719 -0
  59. affinity_sdk-0.4.7/affinity/cli/csv_utils.py +120 -0
  60. affinity_sdk-0.4.7/affinity/cli/errors.py +28 -0
  61. affinity_sdk-0.4.7/affinity/cli/field_utils.py +355 -0
  62. affinity_sdk-0.4.7/affinity/cli/logging.py +100 -0
  63. affinity_sdk-0.4.7/affinity/cli/main.py +184 -0
  64. affinity_sdk-0.4.7/affinity/cli/options.py +46 -0
  65. affinity_sdk-0.4.7/affinity/cli/paths.py +32 -0
  66. affinity_sdk-0.4.7/affinity/cli/progress.py +99 -0
  67. affinity_sdk-0.4.7/affinity/cli/render.py +1194 -0
  68. affinity_sdk-0.4.7/affinity/cli/resolve.py +114 -0
  69. affinity_sdk-0.4.7/affinity/cli/resolvers.py +249 -0
  70. affinity_sdk-0.4.7/affinity/cli/results.py +44 -0
  71. affinity_sdk-0.4.7/affinity/cli/runner.py +152 -0
  72. affinity_sdk-0.4.7/affinity/cli/serialization.py +65 -0
  73. affinity_sdk-0.4.7/affinity/cli/types.py +70 -0
  74. affinity_sdk-0.4.7/affinity/client.py +769 -0
  75. affinity_sdk-0.4.7/affinity/clients/__init__.py +19 -0
  76. affinity_sdk-0.4.7/affinity/clients/http.py +3664 -0
  77. affinity_sdk-0.4.7/affinity/clients/pipeline.py +165 -0
  78. affinity_sdk-0.4.7/affinity/downloads.py +114 -0
  79. affinity_sdk-0.4.7/affinity/exceptions.py +593 -0
  80. affinity_sdk-0.4.7/affinity/filters.py +736 -0
  81. affinity_sdk-0.4.7/affinity/hooks.py +198 -0
  82. affinity_sdk-0.4.7/affinity/inbound_webhooks.py +295 -0
  83. affinity_sdk-0.4.7/affinity/models/__init__.py +163 -0
  84. affinity_sdk-0.4.7/affinity/models/entities.py +793 -0
  85. affinity_sdk-0.4.7/affinity/models/pagination.py +495 -0
  86. affinity_sdk-0.4.7/affinity/models/rate_limit_snapshot.py +48 -0
  87. affinity_sdk-0.4.7/affinity/models/secondary.py +413 -0
  88. affinity_sdk-0.4.7/affinity/models/types.py +627 -0
  89. affinity_sdk-0.4.7/affinity/policies.py +40 -0
  90. affinity_sdk-0.4.7/affinity/progress.py +22 -0
  91. affinity_sdk-0.4.7/affinity/py.typed +0 -0
  92. affinity_sdk-0.4.7/affinity/services/__init__.py +42 -0
  93. affinity_sdk-0.4.7/affinity/services/companies.py +1192 -0
  94. affinity_sdk-0.4.7/affinity/services/lists.py +1641 -0
  95. affinity_sdk-0.4.7/affinity/services/opportunities.py +1093 -0
  96. affinity_sdk-0.4.7/affinity/services/persons.py +1154 -0
  97. affinity_sdk-0.4.7/affinity/services/rate_limits.py +173 -0
  98. affinity_sdk-0.4.7/affinity/services/tasks.py +193 -0
  99. affinity_sdk-0.4.7/affinity/services/v1_only.py +2428 -0
  100. affinity_sdk-0.4.7/affinity/types.py +83 -0
  101. affinity_sdk-0.4.7/docs/cli-development-guide.md +586 -0
  102. affinity_sdk-0.4.7/docs/public/changelog.md +3 -0
  103. affinity_sdk-0.4.7/docs/public/cli/commands.md +703 -0
  104. affinity_sdk-0.4.7/docs/public/cli/index.md +121 -0
  105. affinity_sdk-0.4.7/docs/public/cli/scripting.md +41 -0
  106. affinity_sdk-0.4.7/docs/public/cli-reference.md +523 -0
  107. affinity_sdk-0.4.7/docs/public/examples.md +77 -0
  108. affinity_sdk-0.4.7/docs/public/getting-started.md +120 -0
  109. affinity_sdk-0.4.7/docs/public/glossary.md +22 -0
  110. affinity_sdk-0.4.7/docs/public/guides/api-versions-and-routing.md +46 -0
  111. affinity_sdk-0.4.7/docs/public/guides/authentication.md +52 -0
  112. affinity_sdk-0.4.7/docs/public/guides/claude-code-plugin.md +86 -0
  113. affinity_sdk-0.4.7/docs/public/guides/configuration.md +191 -0
  114. affinity_sdk-0.4.7/docs/public/guides/csv-export.md +313 -0
  115. affinity_sdk-0.4.7/docs/public/guides/debugging-hooks.md +92 -0
  116. affinity_sdk-0.4.7/docs/public/guides/errors-and-retries.md +156 -0
  117. affinity_sdk-0.4.7/docs/public/guides/field-types-and-values.md +126 -0
  118. affinity_sdk-0.4.7/docs/public/guides/field-values.md +150 -0
  119. affinity_sdk-0.4.7/docs/public/guides/filtering.md +197 -0
  120. affinity_sdk-0.4.7/docs/public/guides/ids-and-types.md +21 -0
  121. affinity_sdk-0.4.7/docs/public/guides/models.md +64 -0
  122. affinity_sdk-0.4.7/docs/public/guides/opportunity-associations.md +135 -0
  123. affinity_sdk-0.4.7/docs/public/guides/pagination.md +81 -0
  124. affinity_sdk-0.4.7/docs/public/guides/performance.md +94 -0
  125. affinity_sdk-0.4.7/docs/public/guides/rate-limits.md +36 -0
  126. affinity_sdk-0.4.7/docs/public/guides/sync-vs-async.md +54 -0
  127. affinity_sdk-0.4.7/docs/public/guides/v1-v2-routing.md +3 -0
  128. affinity_sdk-0.4.7/docs/public/guides/webhooks.md +226 -0
  129. affinity_sdk-0.4.7/docs/public/index.md +32 -0
  130. affinity_sdk-0.4.7/docs/public/reference/client.md +9 -0
  131. affinity_sdk-0.4.7/docs/public/reference/exceptions.md +3 -0
  132. affinity_sdk-0.4.7/docs/public/reference/filters.md +3 -0
  133. affinity_sdk-0.4.7/docs/public/reference/models.md +3 -0
  134. affinity_sdk-0.4.7/docs/public/reference/services/companies.md +10 -0
  135. affinity_sdk-0.4.7/docs/public/reference/services/lists.md +5 -0
  136. affinity_sdk-0.4.7/docs/public/reference/services/opportunities.md +5 -0
  137. affinity_sdk-0.4.7/docs/public/reference/services/persons.md +5 -0
  138. affinity_sdk-0.4.7/docs/public/reference/services/tasks.md +5 -0
  139. affinity_sdk-0.4.7/docs/public/reference/services/v1/auth.md +3 -0
  140. affinity_sdk-0.4.7/docs/public/reference/services/v1/field-values.md +3 -0
  141. affinity_sdk-0.4.7/docs/public/reference/services/v1/fields.md +3 -0
  142. affinity_sdk-0.4.7/docs/public/reference/services/v1/files.md +3 -0
  143. affinity_sdk-0.4.7/docs/public/reference/services/v1/interactions.md +3 -0
  144. affinity_sdk-0.4.7/docs/public/reference/services/v1/notes.md +3 -0
  145. affinity_sdk-0.4.7/docs/public/reference/services/v1/relationships.md +3 -0
  146. affinity_sdk-0.4.7/docs/public/reference/services/v1/reminders.md +3 -0
  147. affinity_sdk-0.4.7/docs/public/reference/services/v1/webhooks.md +3 -0
  148. affinity_sdk-0.4.7/docs/public/reference/types.md +3 -0
  149. affinity_sdk-0.4.7/docs/public/troubleshooting.md +22 -0
  150. affinity_sdk-0.4.7/examples/advanced_usage.py +480 -0
  151. affinity_sdk-0.4.7/examples/async_lifecycle.py +43 -0
  152. affinity_sdk-0.4.7/examples/basic_usage.py +74 -0
  153. affinity_sdk-0.4.7/examples/filter_builder.py +36 -0
  154. affinity_sdk-0.4.7/examples/hooks_debugging.py +40 -0
  155. affinity_sdk-0.4.7/examples/list_management.py +120 -0
  156. affinity_sdk-0.4.7/examples/resolve_helpers.py +38 -0
  157. affinity_sdk-0.4.7/examples/task_polling.py +34 -0
  158. affinity_sdk-0.4.7/mkdocs.yml +86 -0
  159. affinity_sdk-0.4.7/pyproject.toml +185 -0
  160. affinity_sdk-0.4.7/tests/conftest.py +180 -0
  161. affinity_sdk-0.4.7/tests/fixtures/webhooks/field_value_updated.json +26 -0
  162. affinity_sdk-0.4.7/tests/fixtures/webhooks/list_entry_created.json +20 -0
  163. affinity_sdk-0.4.7/tests/fixtures/webhooks/organization_created.json +12 -0
  164. affinity_sdk-0.4.7/tests/fixtures/webhooks/organization_merged.json +31 -0
  165. affinity_sdk-0.4.7/tests/fixtures/webhooks/person_created.json +13 -0
  166. affinity_sdk-0.4.7/tests/fixtures/webhooks/unknown_event.json +7 -0
  167. affinity_sdk-0.4.7/tests/test_affinity_client_wrapper_additional_coverage.py +194 -0
  168. affinity_sdk-0.4.7/tests/test_async_file_download_streaming.py +159 -0
  169. affinity_sdk-0.4.7/tests/test_cli_company_get.py +625 -0
  170. affinity_sdk-0.4.7/tests/test_cli_config_cmds.py +434 -0
  171. affinity_sdk-0.4.7/tests/test_cli_csv_export.py +2770 -0
  172. affinity_sdk-0.4.7/tests/test_cli_datetime_formatting.py +40 -0
  173. affinity_sdk-0.4.7/tests/test_cli_datetime_header_offsets.py +50 -0
  174. affinity_sdk-0.4.7/tests/test_cli_domain_linkification.py +24 -0
  175. affinity_sdk-0.4.7/tests/test_cli_entity_files_dump.py +128 -0
  176. affinity_sdk-0.4.7/tests/test_cli_error_rendering.py +111 -0
  177. affinity_sdk-0.4.7/tests/test_cli_field_value_humanization.py +110 -0
  178. affinity_sdk-0.4.7/tests/test_cli_json_safety.py +288 -0
  179. affinity_sdk-0.4.7/tests/test_cli_missing_coverage.py +324 -0
  180. affinity_sdk-0.4.7/tests/test_cli_no_network_commands.py +126 -0
  181. affinity_sdk-0.4.7/tests/test_cli_opportunity_cmds.py +390 -0
  182. affinity_sdk-0.4.7/tests/test_cli_pager_logic.py +55 -0
  183. affinity_sdk-0.4.7/tests/test_cli_person_get.py +114 -0
  184. affinity_sdk-0.4.7/tests/test_cli_progress_manager.py +9 -0
  185. affinity_sdk-0.4.7/tests/test_cli_rate_limit_rendering.py +38 -0
  186. affinity_sdk-0.4.7/tests/test_cli_resolve_url_parsing.py +30 -0
  187. affinity_sdk-0.4.7/tests/test_cli_serialization.py +174 -0
  188. affinity_sdk-0.4.7/tests/test_cli_v1_only_cmds.py +363 -0
  189. affinity_sdk-0.4.7/tests/test_cli_write_ops.py +417 -0
  190. affinity_sdk-0.4.7/tests/test_client.py +1283 -0
  191. affinity_sdk-0.4.7/tests/test_env_helpers.py +68 -0
  192. affinity_sdk-0.4.7/tests/test_field_value_type.py +77 -0
  193. affinity_sdk-0.4.7/tests/test_file_download_streaming.py +338 -0
  194. affinity_sdk-0.4.7/tests/test_file_upload_helpers_and_validation.py +91 -0
  195. affinity_sdk-0.4.7/tests/test_filters.py +390 -0
  196. affinity_sdk-0.4.7/tests/test_http_client_additional_coverage.py +424 -0
  197. affinity_sdk-0.4.7/tests/test_http_client_remaining_coverage.py +1210 -0
  198. affinity_sdk-0.4.7/tests/test_inbound_webhooks.py +144 -0
  199. affinity_sdk-0.4.7/tests/test_integration_smoke.py +36 -0
  200. affinity_sdk-0.4.7/tests/test_interactions_and_files_regressions.py +196 -0
  201. affinity_sdk-0.4.7/tests/test_list_entry_membership_and_opportunities.py +209 -0
  202. affinity_sdk-0.4.7/tests/test_models.py +858 -0
  203. affinity_sdk-0.4.7/tests/test_new_features.py +712 -0
  204. affinity_sdk-0.4.7/tests/test_pagination_iterators.py +486 -0
  205. affinity_sdk-0.4.7/tests/test_raw_pipeline_hook_events.py +359 -0
  206. affinity_sdk-0.4.7/tests/test_requirements_additional.py +211 -0
  207. affinity_sdk-0.4.7/tests/test_services_lists_additional_coverage.py +989 -0
  208. affinity_sdk-0.4.7/tests/test_services_opportunities_additional_coverage.py +747 -0
  209. affinity_sdk-0.4.7/tests/test_services_opportunities_tasks_additional_coverage.py +386 -0
  210. affinity_sdk-0.4.7/tests/test_services_persons_companies_additional_coverage.py +1933 -0
  211. affinity_sdk-0.4.7/tests/test_services_v1_only_async.py +458 -0
  212. affinity_sdk-0.4.7/tests/test_small_modules_coverage.py +176 -0
  213. affinity_sdk-0.4.7/tests/test_transport_and_readonly_mode.py +109 -0
  214. affinity_sdk-0.4.7/tests/test_v1_only_services_additional_coverage.py +1308 -0
  215. affinity_sdk-0.4.7/tests/test_v1_only_services_more_coverage.py +602 -0
  216. affinity_sdk-0.4.7/tools/check_cli_patterns.py +118 -0
  217. affinity_sdk-0.4.7/tools/generate_requirements_mapping.py +140 -0
  218. affinity_sdk-0.4.7/tools/sync_plugin_version.py +48 -0
  219. affinity_sdk-0.4.7/tools/validate_openapi_models.py +436 -0
@@ -0,0 +1,58 @@
1
+ Show a quick reference for using the Affinity Python SDK and xaffinity CLI.
2
+
3
+ ## IMPORTANT: Read-Only by Default
4
+
5
+ Always use read-only mode unless the user explicitly approves data modification. CRM data is sensitive!
6
+
7
+ ## SDK Quick Start
8
+
9
+ ```python
10
+ from affinity import Affinity, F
11
+ from affinity.types import PersonId, CompanyId, ListId
12
+ from affinity.policies import Policies, WritePolicy
13
+
14
+ # ALWAYS use read-only mode by default
15
+ with Affinity.from_env(policies=Policies(write=WritePolicy.DENY)) as client:
16
+ # Identity
17
+ me = client.whoami()
18
+
19
+ # Iterate all companies
20
+ for company in client.companies.all():
21
+ print(company.name)
22
+
23
+ # Filter (custom fields only)
24
+ sales = client.persons.list(filter=F.field("Department").equals("Sales"))
25
+
26
+ # Get by ID (use typed IDs!)
27
+ person = client.persons.get(PersonId(123))
28
+ ```
29
+
30
+ ## CLI Quick Start
31
+
32
+ **Always use: `--dotenv --readonly --json`**
33
+
34
+ ```bash
35
+ # Standard pattern for queries
36
+ xaffinity --dotenv --readonly person search "John Smith" --json
37
+ xaffinity --dotenv --readonly person get 123 --json
38
+ xaffinity --dotenv --readonly company get domain:acme.com --json
39
+
40
+ # Export to CSV (no --json needed)
41
+ xaffinity --dotenv --readonly person ls --all --csv people.csv
42
+
43
+ # Parse JSON with jq
44
+ xaffinity --dotenv --readonly person ls --json --all | jq '.data.persons[]'
45
+ ```
46
+
47
+ ## Key Gotchas
48
+
49
+ 1. **Always use `--json`**: For structured, parseable output
50
+ 2. **Always use `--readonly`**: Only allow writes when user explicitly approves
51
+ 3. **Always use `--dotenv`**: Loads API key from .env file
52
+ 4. **Use typed IDs (SDK)**: `PersonId(123)` not `123`
53
+ 5. **Filters only work on custom fields** - not `type`, `name`, `domain`, etc.
54
+
55
+ ## More Info
56
+
57
+ - SDK docs: https://yaniv-golan.github.io/affinity-sdk/
58
+ - CLI help: `xaffinity --help`
@@ -0,0 +1,17 @@
1
+ {
2
+ "description": "Ensures API key is configured before xaffinity data commands",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Bash",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "bash ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/hooks/pre-xaffinity.sh",
11
+ "timeout": 10
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }
@@ -0,0 +1,36 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ input=$(cat)
5
+ command=$(echo "$input" | jq -r '.tool_input.command // ""')
6
+
7
+ # Not an xaffinity command - allow
8
+ if [[ "$command" != *"xaffinity"* ]]; then
9
+ exit 0
10
+ fi
11
+
12
+ # Config/help commands are always allowed
13
+ if [[ "$command" =~ xaffinity[[:space:]]*(--help|--version) ]] || \
14
+ [[ "$command" =~ xaffinity[[:space:]]+config ]] || \
15
+ [[ "$command" =~ --help ]]; then
16
+ exit 0
17
+ fi
18
+
19
+ # Check if API key is configured
20
+ check_result=$(xaffinity --json config check-key 2>/dev/null || echo '{"data":{"configured":false}}')
21
+ configured=$(echo "$check_result" | jq -r '.data.configured // false')
22
+
23
+ if [ "$configured" = "true" ]; then
24
+ exit 0 # Key configured, allow command
25
+ fi
26
+
27
+ # Not configured - block with guidance
28
+ cat >&2 << 'EOF'
29
+ {
30
+ "hookSpecificOutput": {
31
+ "permissionDecision": "deny"
32
+ },
33
+ "systemMessage": "BLOCKED: Affinity API key not configured. Tell the user to run 'xaffinity config setup-key' to configure (interactive - user must run it themselves). Then retry."
34
+ }
35
+ EOF
36
+ exit 2
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "affinity-sdk",
3
+ "owner": {
4
+ "name": "Yaniv Golan"
5
+ },
6
+ "metadata": {
7
+ "description": "Plugins for the Affinity Python SDK and xaffinity CLI"
8
+ },
9
+ "plugins": [
10
+ {
11
+ "name": "affinity-sdk",
12
+ "source": "./",
13
+ "description": "Knowledge and commands for using the Affinity Python SDK and xaffinity CLI to query and manage Affinity CRM data"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "affinity-sdk",
3
+ "description": "Knowledge and commands for using the Affinity Python SDK and xaffinity CLI to query and manage Affinity CRM data",
4
+ "version": "0.4.7",
5
+ "author": {
6
+ "name": "Yaniv Golan"
7
+ },
8
+ "homepage": "https://github.com/yaniv-golan/affinity-sdk",
9
+ "repository": "https://github.com/yaniv-golan/affinity-sdk",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "affinity",
13
+ "crm",
14
+ "sdk",
15
+ "cli",
16
+ "api"
17
+ ],
18
+ "commands": [
19
+ "./.claude-plugin/commands/"
20
+ ],
21
+ "skills": [
22
+ "./.claude-plugin/skills/"
23
+ ],
24
+ "hooks": [
25
+ "./.claude-plugin/hooks/hooks.json"
26
+ ]
27
+ }
@@ -0,0 +1,349 @@
1
+ # CLI Reference
2
+
3
+ Complete command reference for the `xaffinity` CLI.
4
+
5
+ ## Installation and Setup
6
+
7
+ ```bash
8
+ pip install "affinity-sdk[cli]"
9
+ ```
10
+
11
+ **API Key options (choose one):**
12
+ ```bash
13
+ # Option 1: Environment variable
14
+ export AFFINITY_API_KEY="your-api-key"
15
+
16
+ # Option 2: Use --dotenv to load from .env file in current directory
17
+ xaffinity --dotenv whoami
18
+
19
+ # Option 3: Read from file
20
+ xaffinity --api-key-file ~/.affinity-key whoami
21
+ ```
22
+
23
+ ## Read-Only Mode (IMPORTANT)
24
+
25
+ **Always use `--readonly` by default** to prevent accidental data modification:
26
+
27
+ ```bash
28
+ # RECOMMENDED: Use --readonly for all read operations
29
+ xaffinity --readonly person ls --all
30
+ xaffinity --readonly company get 123
31
+ xaffinity --readonly list export 12345 --all --csv entries.csv
32
+
33
+ # Only omit --readonly when user explicitly approves writes
34
+ xaffinity person create --first-name Ada --last-name Lovelace
35
+ ```
36
+
37
+ ## Global Options
38
+
39
+ | Option | Description |
40
+ |--------|-------------|
41
+ | `--readonly` | **Recommended** - Prevent write operations |
42
+ | `--json` | Output machine-readable JSON |
43
+ | `--quiet` / `-q` | Suppress non-essential output |
44
+ | `--trace` | Show request/response traces |
45
+ | `--beta` | Enable beta endpoints |
46
+ | `--all` | Fetch all pages |
47
+ | `--csv <path>` | Export to CSV (requires `--all`) |
48
+ | `--csv-bom` | Add UTF-8 BOM for Excel |
49
+
50
+ ## Timezone Note
51
+
52
+ **All datetimes from the API are in UTC.** When displaying to users:
53
+ - Convert to user's local timezone or clearly state "UTC"
54
+ - Example: `2025-12-30T13:00:00Z` (UTC) = 3:00 PM Israel time, 5:00 AM PST
55
+
56
+ ## Identity
57
+
58
+ ```bash
59
+ xaffinity whoami
60
+ xaffinity whoami --json
61
+ ```
62
+
63
+ ## People
64
+
65
+ ```bash
66
+ # List all
67
+ xaffinity person ls
68
+ xaffinity person ls --all
69
+ xaffinity person ls --all --csv people.csv
70
+
71
+ # Search
72
+ xaffinity person search "alice@example.com"
73
+ xaffinity person search "Alice Smith" --all
74
+
75
+ # Get by ID
76
+ xaffinity person get 123
77
+ xaffinity person get 123 --all-fields
78
+
79
+ # Get by resolver
80
+ xaffinity person get email:alice@example.com
81
+ xaffinity person get 'name:"Alice Smith"'
82
+
83
+ # Get with expansions
84
+ xaffinity person get 123 --expand lists
85
+ xaffinity person get 123 --expand list-entries --list "Pipeline"
86
+ xaffinity person get 123 --expand list-entries --list-entry-field Stage
87
+
88
+ # Create
89
+ xaffinity person create --first-name Ada --last-name Lovelace --email ada@example.com
90
+
91
+ # Update
92
+ xaffinity person update 123 --email new@example.com
93
+
94
+ # Delete
95
+ xaffinity person delete 123
96
+
97
+ # Merge (requires --beta)
98
+ xaffinity --beta person merge 111 222
99
+ ```
100
+
101
+ ## Companies
102
+
103
+ ```bash
104
+ # List all
105
+ xaffinity company ls
106
+ xaffinity company ls --all --csv companies.csv
107
+
108
+ # Search
109
+ xaffinity company search "Acme"
110
+
111
+ # Get by ID
112
+ xaffinity company get 456
113
+ xaffinity company get 456 --all-fields
114
+
115
+ # Get by resolver
116
+ xaffinity company get domain:acme.com
117
+ xaffinity company get 'name:"Acme Corp"'
118
+
119
+ # Get with expansions
120
+ xaffinity company get 456 --expand lists
121
+ xaffinity company get 456 --expand people
122
+
123
+ # Create
124
+ xaffinity company create --name "New Corp" --domain newcorp.com
125
+
126
+ # Update
127
+ xaffinity company update 456 --domain updated.com
128
+
129
+ # Delete
130
+ xaffinity company delete 456
131
+ ```
132
+
133
+ ## Lists
134
+
135
+ ```bash
136
+ # List all lists
137
+ xaffinity list ls
138
+
139
+ # Get list details
140
+ xaffinity list view 789
141
+ xaffinity list view 'name:"Deal Pipeline"'
142
+
143
+ # Get list fields
144
+ xaffinity list fields 789
145
+
146
+ # Export list entries
147
+ xaffinity list export 789 --all
148
+ xaffinity list export 789 --all --csv entries.csv
149
+ ```
150
+
151
+ ### List Export with Expansions
152
+
153
+ Export list entries with associated people/companies using `--expand`:
154
+
155
+ ```bash
156
+ # Export with associated people
157
+ xaffinity list export LIST_ID --expand people --all --csv output.csv --csv-bom
158
+
159
+ # Export with both people and companies (opportunity lists)
160
+ xaffinity list export LIST_ID --expand people --expand companies --all --csv output.csv
161
+
162
+ # Combine with filters
163
+ xaffinity list export LIST_ID --expand people \
164
+ --filter 'Status = "Active"' \
165
+ --all --csv active_with_people.csv --csv-bom
166
+ ```
167
+
168
+ **Valid expand values:** `people` (opportunity/company lists), `companies` (opportunity/person lists)
169
+
170
+ See [LIST_EXPORT_EXPAND.md](LIST_EXPORT_EXPAND.md) for detailed options: CSV modes, field expansion, association limits, error handling.
171
+
172
+ See [Filtering](#filtering) section for filter syntax reference.
173
+
174
+ ## Opportunities
175
+
176
+ ```bash
177
+ # List all
178
+ xaffinity opportunity ls
179
+ xaffinity opportunity ls --all --csv opps.csv
180
+
181
+ # Get by ID
182
+ xaffinity opportunity get 321
183
+
184
+ # Create (requires list ID)
185
+ xaffinity opportunity create --list-id 789 --name "New Deal"
186
+ ```
187
+
188
+ ## Field Values
189
+
190
+ ```bash
191
+ # List field values
192
+ xaffinity field-value ls --field-id 123
193
+
194
+ # Get specific value
195
+ xaffinity field-value get 456
196
+
197
+ # Create/update
198
+ xaffinity field-value create --field-id 123 --entity-id 456 --value "New Value"
199
+ xaffinity field-value update 789 --value "Updated"
200
+
201
+ # Delete
202
+ xaffinity field-value delete 789
203
+ ```
204
+
205
+ ## Notes
206
+
207
+ ```bash
208
+ # List notes
209
+ xaffinity note ls --person-id 123
210
+ xaffinity note ls --company-id 456
211
+
212
+ # Create note
213
+ xaffinity note create --person-id 123 --content "Meeting notes"
214
+ ```
215
+
216
+ ## Interactions (Meetings, Emails, Calls)
217
+
218
+ **Use interactions for meetings, emails, and calls** - auto-synced from calendars and email.
219
+
220
+ **CRITICAL requirements:**
221
+ - **Both `--start-time` AND `--end-time` are REQUIRED** (API will error without them)
222
+ - **Maximum date range is 1 year** - start and end must be within 1 year of each other
223
+ - Interactions only appear if **at least one external contact** was involved
224
+ - Internal-only meetings (team members only) won't appear unless logged with a note
225
+ - Sync lag: Google Calendar ~30 min, Office365 up to 2 hours
226
+
227
+ ```bash
228
+ # List meetings for a person in 2025 (MUST specify date range, max 1 year)
229
+ xaffinity --dotenv --readonly interaction ls --person-id 123 --type meeting \
230
+ --start-time 2025-01-01 --end-time 2025-12-31 --json
231
+
232
+ # For multiple years, query each year separately
233
+ xaffinity --dotenv --readonly interaction ls --person-id 123 --type meeting \
234
+ --start-time 2024-01-01 --end-time 2024-12-31 --json
235
+
236
+ # Other interaction types (same date range requirements)
237
+ xaffinity --dotenv --readonly interaction ls --person-id 123 --type email \
238
+ --start-time 2025-01-01 --end-time 2025-12-31 --json
239
+ xaffinity --dotenv --readonly interaction ls --person-id 123 --type call \
240
+ --start-time 2025-01-01 --end-time 2025-12-31 --json
241
+ ```
242
+
243
+ Interaction types: `meeting`, `email`, `call`, `chat-message` (or `chat`)
244
+
245
+ **Alternatives for internal users:**
246
+ - Use notes with `isMeeting: true` for meeting records
247
+ - Use Smart Fields (`Last Meeting`, `Next Meeting`) on person/company records
248
+
249
+ ## URL Resolution
250
+
251
+ ```bash
252
+ # Parse Affinity UI URLs
253
+ xaffinity resolve-url "https://app.affinity.co/companies/123"
254
+ xaffinity resolve-url "https://mydomain.affinity.com/persons/456"
255
+ ```
256
+
257
+ ## Filtering
258
+
259
+ **Custom fields only** (built-in properties like `type`, `name`, `domain` cannot be filtered server-side):
260
+
261
+ ```bash
262
+ # Equals (quote field names with spaces)
263
+ xaffinity person ls --filter 'Department = "Sales"' --all
264
+ xaffinity person ls --filter '"Primary Email Status" = "Valid"' --all
265
+
266
+ # Contains
267
+ xaffinity company ls --filter 'Industry =~ "Tech"' --all
268
+
269
+ # Starts with
270
+ xaffinity person ls --filter 'Title =^ "VP"' --all
271
+
272
+ # AND / OR
273
+ xaffinity person ls --filter 'Dept = "Sales" & Status = "Active"' --all
274
+ xaffinity company ls --filter 'Region = "US" | Region = "EU"' --all
275
+
276
+ # NOT
277
+ xaffinity person ls --filter '!(Archived = true)' --all
278
+
279
+ # NULL checks
280
+ xaffinity person ls --filter 'Manager != *' --all # is null
281
+ xaffinity person ls --filter 'Manager = *' --all # is not null
282
+ ```
283
+
284
+ ## Filter Operators Reference
285
+
286
+ | Meaning | Operator | Example |
287
+ |---------|----------|---------|
288
+ | equals | `=` | `Status = "Active"` |
289
+ | not equals | `!=` | `Status != "Closed"` |
290
+ | contains | `=~` | `Name =~ "Corp"` |
291
+ | starts with | `=^` | `Name =^ "Ac"` |
292
+ | ends with | `=$` | `Name =$ "Inc"` |
293
+ | is null | `!= *` | `Email != *` |
294
+ | is not null | `= *` | `Email = *` |
295
+ | is empty | `= ""` | `Notes = ""` |
296
+ | AND | `&` | `A = 1 & B = 2` |
297
+ | OR | `\|` | `A = 1 \| B = 2` |
298
+ | NOT | `!` | `!(A = 1)` |
299
+
300
+ **Note:** Field names with spaces must be quoted: `"Primary Email Status" = "Valid"`
301
+
302
+ ## JSON Output for Scripting
303
+
304
+ All commands support `--json` for machine-readable output:
305
+
306
+ ```bash
307
+ # Get JSON
308
+ xaffinity person ls --json --all
309
+
310
+ # Pipe to jq
311
+ xaffinity person ls --json --all | jq '.data.persons[]'
312
+ xaffinity company get 123 --json | jq '.data.company.name'
313
+
314
+ # Filter built-in properties with jq (since --filter only works on custom fields)
315
+ xaffinity person ls --json --all | jq '.data.persons[] | select(.type == "internal")'
316
+ xaffinity company ls --json --all | jq '.data.companies[] | select(.domain | endswith(".com"))'
317
+
318
+ # Custom CSV with jq
319
+ xaffinity person ls --json --all | jq -r '.data.persons[] | [.id, .name, .primaryEmail] | @csv'
320
+ ```
321
+
322
+ ## JSON Data Structure
323
+
324
+ ```json
325
+ {
326
+ "data": {
327
+ "persons": [...], // for person ls
328
+ "companies": [...], // for company ls
329
+ "opportunities": [...],
330
+ "person": {...}, // for person get
331
+ "company": {...} // for company get
332
+ }
333
+ }
334
+ ```
335
+
336
+ ## CSV Export
337
+
338
+ ```bash
339
+ # Basic export
340
+ xaffinity person ls --all --csv people.csv
341
+ xaffinity company ls --all --csv companies.csv
342
+ xaffinity opportunity ls --all --csv opps.csv
343
+ xaffinity list export 123 --all --csv entries.csv
344
+
345
+ # Excel-compatible (UTF-8 BOM)
346
+ xaffinity person ls --all --csv people.csv --csv-bom
347
+ ```
348
+
349
+ **Note**: `--csv` requires `--all` to fetch all pages.
@@ -0,0 +1,177 @@
1
+ # List Export with Expansions
2
+
3
+ Export list entries along with their associated entities (people, companies, opportunities).
4
+
5
+ ## Valid Expand Values by List Type
6
+
7
+ | List Type | Valid `--expand` Values |
8
+ |-----------|------------------------|
9
+ | opportunity | `people`, `companies` |
10
+ | person | `companies`, `opportunities` |
11
+ | company | `people`, `opportunities` |
12
+
13
+ Using an invalid expand value for the list type fails with exit code 2.
14
+
15
+ ## Basic Usage
16
+
17
+ ```bash
18
+ # Export with associated people
19
+ xaffinity list export LIST_ID --expand people --all --csv output.csv --csv-bom
20
+
21
+ # Export with both people and companies (opportunity lists)
22
+ xaffinity list export LIST_ID --expand people --expand companies --all --csv output.csv
23
+
24
+ # Combine with filters
25
+ xaffinity list export LIST_ID --expand people \
26
+ --filter 'Status = "Active"' \
27
+ --all --csv active_with_people.csv --csv-bom
28
+ ```
29
+
30
+ ## Controlling Association Limits
31
+
32
+ By default, up to 100 associations are fetched per entry per expand type.
33
+
34
+ ```bash
35
+ # Limit associations per entry (default: 100)
36
+ xaffinity list export LIST_ID --expand people --expand-max-results 50 --all --csv output.csv
37
+
38
+ # Fetch ALL associations (no limit) - use for complete data
39
+ xaffinity list export LIST_ID --expand people --expand-all --all --csv output.csv
40
+ ```
41
+
42
+ **Note:** `--expand-max-results` applies per expand type per entry. With `--expand people --expand companies --expand-max-results 10`, you get up to 10 people AND up to 10 companies per entry.
43
+
44
+ ## CSV Output Modes
45
+
46
+ ### Flat Mode (default)
47
+
48
+ One row per association - easier to filter in spreadsheets:
49
+
50
+ ```bash
51
+ xaffinity list export LIST_ID --expand people --csv-mode flat --all --csv flat.csv
52
+ ```
53
+
54
+ Output:
55
+ ```csv
56
+ listEntryId,entityName,Status,expandedType,expandedId,expandedName,expandedEmail
57
+ 123,Big Deal,Active,person,789,Alice Smith,alice@example.com
58
+ 123,Big Deal,Active,person,790,Bob Jones,bob@example.com
59
+ 123,Big Deal,Active,company,101,Acme Corp,
60
+ 124,Small Deal,Pending,company,102,Beta Inc,
61
+ ```
62
+
63
+ ### Nested Mode
64
+
65
+ One row per entry with JSON arrays in columns:
66
+
67
+ ```bash
68
+ xaffinity list export LIST_ID --expand people --csv-mode nested --all --csv nested.csv
69
+ ```
70
+
71
+ Output:
72
+ ```csv
73
+ listEntryId,entityName,Status,_expand_people
74
+ 123,Big Deal,Active,"[{""id"": 789, ""name"": ""Alice Smith""}]"
75
+ ```
76
+
77
+ **Warning:** `--expand-all` with `--csv-mode nested` can be memory-intensive for entries with many associations.
78
+
79
+ ## Expanding Entity Fields (Phase 4)
80
+
81
+ By default, only core fields are included (id, name, email/domain). To include additional fields:
82
+
83
+ ```bash
84
+ # Include specific fields on expanded entities
85
+ xaffinity list export LIST_ID --expand people \
86
+ --expand-fields "Title" --expand-fields "Department" \
87
+ --all --csv output.csv
88
+
89
+ # Include all fields of a type (global, enriched, relationship-intelligence)
90
+ xaffinity list export LIST_ID --expand people \
91
+ --expand-field-type enriched \
92
+ --all --csv output.csv
93
+
94
+ # Combine both (union of specified fields and field types)
95
+ xaffinity list export LIST_ID --expand people \
96
+ --expand-fields "Custom Status" --expand-field-type enriched \
97
+ --all --csv output.csv
98
+ ```
99
+
100
+ ## Expanding Opportunities (Phase 5)
101
+
102
+ For person and company lists, you can expand associated opportunities:
103
+
104
+ ```bash
105
+ # Expand opportunities for all people on a person list
106
+ xaffinity list export LIST_ID --expand opportunities --all --csv output.csv
107
+
108
+ # Scope to a specific opportunity list (recommended for performance)
109
+ xaffinity list export LIST_ID --expand opportunities \
110
+ --expand-opportunities-list "Pipeline" \
111
+ --all --csv output.csv
112
+ ```
113
+
114
+ **Performance Warning:** Without `--expand-opportunities-list`, the CLI searches ALL opportunity lists you have access to. This can be very slow for large workspaces. Always specify `--expand-opportunities-list` when possible.
115
+
116
+ ## Filtering Expanded Associations (Phase 5)
117
+
118
+ Filter which associations are included based on field values:
119
+
120
+ ```bash
121
+ # Only include people named "Alice"
122
+ xaffinity list export LIST_ID --expand people \
123
+ --expand-filter "name=Alice" \
124
+ --all --csv output.csv
125
+
126
+ # Exclude inactive associations
127
+ xaffinity list export LIST_ID --expand people \
128
+ --expand-filter "status!=Inactive" \
129
+ --all --csv output.csv
130
+
131
+ # Multiple conditions (AND logic)
132
+ xaffinity list export LIST_ID --expand people \
133
+ --expand-filter "name=Alice,primaryEmail!=none" \
134
+ --all --csv output.csv
135
+ ```
136
+
137
+ Supported operators:
138
+ - `field=value` - exact match
139
+ - `field!=value` - not equal
140
+
141
+ Multiple conditions are separated by `,` or `;` and use AND logic.
142
+
143
+ ## Error Handling
144
+
145
+ ```bash
146
+ # Default: stop on first error
147
+ xaffinity list export LIST_ID --expand people --all --csv output.csv
148
+
149
+ # Continue on per-entry errors (skip failed entries)
150
+ xaffinity list export LIST_ID --expand people --expand-on-error skip --all --csv output.csv
151
+ ```
152
+
153
+ Use `--expand-on-error skip` for permissive exports where partial data is acceptable.
154
+
155
+ ## Important Constraints
156
+
157
+ 1. **No cursor with expand:** `--cursor` cannot be combined with `--expand`. Use `--all` for complete exports.
158
+
159
+ 2. **Memory considerations:** `--expand-all` with `--csv-mode nested` loads all associations into memory per entry. For entries with hundreds of associations, prefer `--csv-mode flat`.
160
+
161
+ 3. **Truncation warnings:** When associations are truncated due to `--expand-max-results`, a warning is emitted at the end of the export.
162
+
163
+ 4. **Opportunities expansion is expensive:** Without `--expand-opportunities-list`, expanding opportunities requires searching all opportunity lists, which can be very slow.
164
+
165
+ ## Options Reference
166
+
167
+ | Option | Type | Default | Description |
168
+ |--------|------|---------|-------------|
169
+ | `--expand` | choice (repeatable) | - | Expand type: `people`, `companies`, or `opportunities` |
170
+ | `--expand-max-results` | int | 100 | Max associations per entry per type |
171
+ | `--expand-all` | flag | false | Fetch all associations (no limit) |
172
+ | `--expand-fields` | string (repeatable) | - | Specific fields to include |
173
+ | `--expand-field-type` | choice (repeatable) | - | Field types: `global`, `enriched`, `relationship-intelligence` |
174
+ | `--expand-on-error` | choice | `raise` | Error handling: `raise` or `skip` |
175
+ | `--csv-mode` | choice | `flat` | CSV format: `flat` or `nested` |
176
+ | `--expand-filter` | string | - | Filter associations (e.g., `field=value`) |
177
+ | `--expand-opportunities-list` | string | - | Scope `--expand opportunities` to a specific list |