autotouch-cli 0.2.24__tar.gz → 0.2.26__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 (64) hide show
  1. {autotouch_cli-0.2.24/autotouch_cli.egg-info → autotouch_cli-0.2.26}/PKG-INFO +78 -2
  2. autotouch_cli-0.2.24/docs/research-table/reference/autotouch-cli.md → autotouch_cli-0.2.26/autotouch_cli.egg-info/PKG-INFO +86 -1
  3. autotouch_cli-0.2.24/PKG-INFO → autotouch_cli-0.2.26/docs/research-table/reference/autotouch-cli.md +77 -10
  4. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/pyproject.toml +1 -1
  5. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/smart_table_cli.py +398 -0
  6. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/autotouch_cli.egg-info/SOURCES.txt +0 -0
  7. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/autotouch_cli.egg-info/dependency_links.txt +0 -0
  8. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/autotouch_cli.egg-info/entry_points.txt +0 -0
  9. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/autotouch_cli.egg-info/requires.txt +0 -0
  10. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/autotouch_cli.egg-info/top_level.txt +0 -0
  11. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/__init__.py +0 -0
  12. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/add_column_unique_index.py +0 -0
  13. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/attach_csv_import_leads_to_research_table.py +0 -0
  14. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/bundle_sequences_backend.py +0 -0
  15. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/check_agent_traces.py +0 -0
  16. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/check_column_mode.py +0 -0
  17. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/exit_terminal_leads_from_sequences.py +0 -0
  18. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/fetch_lead.py +0 -0
  19. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/fix_lead_titles_from_csv.py +0 -0
  20. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250106_add_column_position.py +0 -0
  21. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250108_fix_legacy_column_fields.py +0 -0
  22. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250109_add_user_fields_to_tables.py +0 -0
  23. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250117_add_call_logs_webhook_indexes.py +0 -0
  24. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250117_rename_call_logs_collection.py +0 -0
  25. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250119_create_leads_unique_email_index.py +0 -0
  26. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250123_add_filter_indexes.py +0 -0
  27. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250123_add_llm_responses_collection.py +0 -0
  28. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250128_migrate_user_ids_to_objectid.py +0 -0
  29. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250208_backfill_task_research_values.py +0 -0
  30. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250604_add_origin_indexes.py +0 -0
  31. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250608_cleanup_agent_metadata.py +0 -0
  32. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250608_rename_agent_metadata_to_metadata.py +0 -0
  33. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250922_add_activity_indexes.py +0 -0
  34. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250926_migrate_single_to_arrays.py +0 -0
  35. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250928_add_missing_timestamp_fields.py +0 -0
  36. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250929_add_task_join_indexes.py +0 -0
  37. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250929_add_task_join_indexes_safe.py +0 -0
  38. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20250929_create_shared_phone_cache.py +0 -0
  39. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20251007_add_rows_position_id_index.py +0 -0
  40. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20251109_add_ttl_for_llm_and_preview_traces.py +0 -0
  41. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20260113_normalize_table_filter_operators.py +0 -0
  42. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20260113_set_user_permissions_user_admin.py +0 -0
  43. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20260204_sync_lead_owner_from_tasks.py +0 -0
  44. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20260303_add_webhook_subscription_collections.py +0 -0
  45. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/20260305_force_formatter_autorun_on_source_update.py +0 -0
  46. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/migrate_org_user_credits.py +0 -0
  47. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/set_default_lead_status.py +0 -0
  48. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/migrations/update_lead_owner_from_tasks.py +0 -0
  49. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/reassign_sequence_owner.py +0 -0
  50. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/run_sidecar_orchestrator_demo.py +0 -0
  51. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/test_crm_company_policy.py +0 -0
  52. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/test_sequences_instantly_e2e.py +0 -0
  53. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/test_sequences_personal_e2e.py +0 -0
  54. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/test_task_error_logger.py +0 -0
  55. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/scripts/verify_azurite_voicemail.py +0 -0
  56. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/setup.cfg +0 -0
  57. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/tests/test_contactout_custom.py +0 -0
  58. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/tests/test_contactout_integration.py +0 -0
  59. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/tests/test_contactout_multi_titles.py +0 -0
  60. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/tests/test_contactout_pipeline.py +0 -0
  61. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/tests/test_contactout_simple.py +0 -0
  62. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/tests/test_contactout_v2_bulk.py +0 -0
  63. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/tests/test_lead_required_fields.py +0 -0
  64. {autotouch_cli-0.2.24 → autotouch_cli-0.2.26}/tests/test_phone_provider_pipeline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autotouch-cli
3
- Version: 0.2.24
3
+ Version: 0.2.26
4
4
  Summary: Autotouch Smart Table CLI
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -37,12 +37,13 @@ Use this order when you are orienting in the CLI:
37
37
  | verify whether a job is really done | `autotouch jobs get --job-id <JOB_ID>` |
38
38
  | create projections from JSON output | `autotouch columns projections` |
39
39
  | work with sequences/tasks | raw HTTP plus `docs/platform/external-workflows-api.md` |
40
+ | query/filter CRM leads and attached research | `autotouch leads query` then `autotouch leads research` |
40
41
 
41
42
  ### Operating model
42
43
 
43
44
  - This file is the full CLI reference and the package readme published to PyPI.
44
45
  - The installed package gives you CLI entrypoints and package metadata; do not assume there is a separate installed docs directory.
45
- - Research-table APIs are the primary CLI surface today; workflow APIs (sequences/tasks) are still HTTP-first.
46
+ - Research-table APIs and read-only leads queries are the primary CLI surface today; workflow APIs (sequences/tasks) are still HTTP-first.
46
47
  - For async operations, backend bulk-job state is authoritative; local terminal output is only a convenience layer.
47
48
  - For staged or cost-sensitive runs, estimate first and prefer filtered scopes plus `firstN` or `run-next`.
48
49
 
@@ -121,17 +122,37 @@ Notes:
121
122
  | `GET /api/tables/{table_id}/columns` | `autotouch columns list --table-id <TABLE_ID>` |
122
123
  | `POST /api/tables/{table_id}/columns` | `autotouch columns create --table-id <TABLE_ID> --data-file column.json` |
123
124
  | `PATCH /api/tables/{table_id}/columns/{column_id}` | `autotouch columns update --table-id <TABLE_ID> --column-id <COLUMN_ID> --data-file column-update.json` |
125
+ | `DELETE /api/tables/{table_id}/columns/{column_id}` | `autotouch columns delete --table-id <TABLE_ID> --column-id <COLUMN_ID> --yes` |
124
126
  | `POST /api/tables/{table_id}/columns/projections` | `autotouch columns projections --table-id <TABLE_ID> --data-file projections.json` |
125
127
  | `POST /api/tables/{table_id}/columns/{column_id}/estimate` | `autotouch columns estimate --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope all` |
126
128
  | `POST /api/tables/{table_id}/columns/{column_id}/run` | `autotouch columns run --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope all` |
127
129
  | `POST /api/tables/{table_id}/columns/{column_id}/stop` | `autotouch columns stop --table-id <TABLE_ID> --column-id <COLUMN_ID>` |
128
130
  | `GET /api/bulk-jobs` | `autotouch jobs list --table-id <TABLE_ID> --column-id <COLUMN_ID> --limit 10` |
129
131
  | `GET /api/bulk-jobs/{job_id}` | `autotouch jobs get --job-id <JOB_ID>` |
132
+ | `POST /api/leads/query` | `autotouch leads query --filter-file lead-filter.json --limit 50` |
133
+ | `POST /api/leads/query/count` | `autotouch leads count --filter-file lead-filter.json` |
134
+ | `POST /api/leads/query/stats` | `autotouch leads stats --filter-file lead-filter.json` |
135
+ | `POST /api/leads/query/research` | `autotouch leads research --lead-id <LEAD_ID> --source-table-id <TABLE_ID>` |
136
+ | `GET /api/leads/filters/metadata` | `autotouch leads filters-metadata` |
137
+ | `GET /api/leads/research-tables/available` | `autotouch leads research-tables` |
130
138
  | `GET /api/tables/{table_id}/webhook` | `autotouch webhooks get --table-id <TABLE_ID>` |
131
139
  | `POST /api/tables/{table_id}/webhook` | `autotouch webhooks rotate --table-id <TABLE_ID>` |
132
140
  | `POST /api/webhooks/tables/{table_id}/ingest` | `autotouch webhooks ingest --table-id <TABLE_ID> --records-file records.json --webhook-token <WEBHOOK_TOKEN>` |
133
141
  | `POST /api/auth/agent-bootstrap` | HTTP-only bootstrap (no direct CLI wrapper yet) |
134
142
 
143
+ ## Delete column
144
+
145
+ Delete is a hard delete:
146
+ - removes the column definition
147
+ - removes all cells stored under that column
148
+ - requires `columns:write`
149
+
150
+ CLI:
151
+
152
+ ```bash
153
+ autotouch columns delete --table-id <TABLE_ID> --column-id <COLUMN_ID> --yes
154
+ ```
155
+
135
156
  ## Workflow API coverage (sequences/tasks)
136
157
 
137
158
  Sequences/tasks APIs are supported by the backend developer-key model but do not yet have dedicated `autotouch` CLI commands.
@@ -154,6 +175,61 @@ Signature controls for sequence payloads:
154
175
  - `steps[].appendSignature` (optional, step-level) overrides signature behavior for a specific email step.
155
176
  - Signature append is deterministic at send time when enabled for the step.
156
177
 
178
+ ## Leads API coverage
179
+
180
+ The CLI now exposes the public read-only leads endpoints under `autotouch leads`.
181
+
182
+ Use this pattern:
183
+
184
+ 1. `autotouch leads query` to page through leads, apply descriptor filters, and inspect `related_tables` / `research_summary`.
185
+ 2. `autotouch leads research` when you need actual research field values for specific lead IDs from one source table.
186
+ 3. `autotouch leads count` / `autotouch leads stats` when you need sizing before a downstream workflow.
187
+
188
+ Important contract notes:
189
+ - External callers should use `POST /api/leads/query` rather than the older `GET /api/leads` list route.
190
+ - `autotouch leads query` accepts the same descriptor payload shape as the backend:
191
+ - `filter`
192
+ - `search`
193
+ - `sort`
194
+ - `pagination`
195
+ - `scope`
196
+ - optional research context (`source_table_id`, `source_table_scope`, `related_tables_mode`)
197
+ - Research values are best treated as a follow-up call:
198
+ - `query` returns lead rows plus research linkage metadata
199
+ - `research` returns `research_data_by_id` keyed by lead id for one `source_table_id`
200
+
201
+ Examples:
202
+
203
+ ```bash
204
+ autotouch leads query \
205
+ --filter-json '{"root":{"kind":"rule","field":"status","operator":"eq","value":"new"}}' \
206
+ --limit 25
207
+ ```
208
+
209
+ ```bash
210
+ autotouch leads count \
211
+ --search "fintech" \
212
+ --scope org
213
+ ```
214
+
215
+ ```bash
216
+ autotouch leads stats \
217
+ --source-table-id <TABLE_ID> \
218
+ --source-table-scope company
219
+ ```
220
+
221
+ ```bash
222
+ autotouch leads research \
223
+ --lead-id <LEAD_ID_1> \
224
+ --lead-id <LEAD_ID_2> \
225
+ --source-table-id <TABLE_ID>
226
+ ```
227
+
228
+ ```bash
229
+ autotouch leads filters-metadata
230
+ autotouch leads research-tables
231
+ ```
232
+
157
233
  ## Bulk job status contract (authoritative run state)
158
234
 
159
235
  Use bulk jobs as the source of truth for run lifecycle:
@@ -1,3 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: autotouch-cli
3
+ Version: 0.2.26
4
+ Summary: Autotouch Smart Table CLI
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: requests>=2.31.0
8
+ Requires-Dist: python-dotenv>=1.0.0
9
+
1
10
  # Autotouch CLI Reference (`autotouch`)
2
11
 
3
12
  This page documents the installable CLI for the Smart Table developer API.
@@ -28,12 +37,13 @@ Use this order when you are orienting in the CLI:
28
37
  | verify whether a job is really done | `autotouch jobs get --job-id <JOB_ID>` |
29
38
  | create projections from JSON output | `autotouch columns projections` |
30
39
  | work with sequences/tasks | raw HTTP plus `docs/platform/external-workflows-api.md` |
40
+ | query/filter CRM leads and attached research | `autotouch leads query` then `autotouch leads research` |
31
41
 
32
42
  ### Operating model
33
43
 
34
44
  - This file is the full CLI reference and the package readme published to PyPI.
35
45
  - The installed package gives you CLI entrypoints and package metadata; do not assume there is a separate installed docs directory.
36
- - Research-table APIs are the primary CLI surface today; workflow APIs (sequences/tasks) are still HTTP-first.
46
+ - Research-table APIs and read-only leads queries are the primary CLI surface today; workflow APIs (sequences/tasks) are still HTTP-first.
37
47
  - For async operations, backend bulk-job state is authoritative; local terminal output is only a convenience layer.
38
48
  - For staged or cost-sensitive runs, estimate first and prefer filtered scopes plus `firstN` or `run-next`.
39
49
 
@@ -112,17 +122,37 @@ Notes:
112
122
  | `GET /api/tables/{table_id}/columns` | `autotouch columns list --table-id <TABLE_ID>` |
113
123
  | `POST /api/tables/{table_id}/columns` | `autotouch columns create --table-id <TABLE_ID> --data-file column.json` |
114
124
  | `PATCH /api/tables/{table_id}/columns/{column_id}` | `autotouch columns update --table-id <TABLE_ID> --column-id <COLUMN_ID> --data-file column-update.json` |
125
+ | `DELETE /api/tables/{table_id}/columns/{column_id}` | `autotouch columns delete --table-id <TABLE_ID> --column-id <COLUMN_ID> --yes` |
115
126
  | `POST /api/tables/{table_id}/columns/projections` | `autotouch columns projections --table-id <TABLE_ID> --data-file projections.json` |
116
127
  | `POST /api/tables/{table_id}/columns/{column_id}/estimate` | `autotouch columns estimate --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope all` |
117
128
  | `POST /api/tables/{table_id}/columns/{column_id}/run` | `autotouch columns run --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope all` |
118
129
  | `POST /api/tables/{table_id}/columns/{column_id}/stop` | `autotouch columns stop --table-id <TABLE_ID> --column-id <COLUMN_ID>` |
119
130
  | `GET /api/bulk-jobs` | `autotouch jobs list --table-id <TABLE_ID> --column-id <COLUMN_ID> --limit 10` |
120
131
  | `GET /api/bulk-jobs/{job_id}` | `autotouch jobs get --job-id <JOB_ID>` |
132
+ | `POST /api/leads/query` | `autotouch leads query --filter-file lead-filter.json --limit 50` |
133
+ | `POST /api/leads/query/count` | `autotouch leads count --filter-file lead-filter.json` |
134
+ | `POST /api/leads/query/stats` | `autotouch leads stats --filter-file lead-filter.json` |
135
+ | `POST /api/leads/query/research` | `autotouch leads research --lead-id <LEAD_ID> --source-table-id <TABLE_ID>` |
136
+ | `GET /api/leads/filters/metadata` | `autotouch leads filters-metadata` |
137
+ | `GET /api/leads/research-tables/available` | `autotouch leads research-tables` |
121
138
  | `GET /api/tables/{table_id}/webhook` | `autotouch webhooks get --table-id <TABLE_ID>` |
122
139
  | `POST /api/tables/{table_id}/webhook` | `autotouch webhooks rotate --table-id <TABLE_ID>` |
123
140
  | `POST /api/webhooks/tables/{table_id}/ingest` | `autotouch webhooks ingest --table-id <TABLE_ID> --records-file records.json --webhook-token <WEBHOOK_TOKEN>` |
124
141
  | `POST /api/auth/agent-bootstrap` | HTTP-only bootstrap (no direct CLI wrapper yet) |
125
142
 
143
+ ## Delete column
144
+
145
+ Delete is a hard delete:
146
+ - removes the column definition
147
+ - removes all cells stored under that column
148
+ - requires `columns:write`
149
+
150
+ CLI:
151
+
152
+ ```bash
153
+ autotouch columns delete --table-id <TABLE_ID> --column-id <COLUMN_ID> --yes
154
+ ```
155
+
126
156
  ## Workflow API coverage (sequences/tasks)
127
157
 
128
158
  Sequences/tasks APIs are supported by the backend developer-key model but do not yet have dedicated `autotouch` CLI commands.
@@ -145,6 +175,61 @@ Signature controls for sequence payloads:
145
175
  - `steps[].appendSignature` (optional, step-level) overrides signature behavior for a specific email step.
146
176
  - Signature append is deterministic at send time when enabled for the step.
147
177
 
178
+ ## Leads API coverage
179
+
180
+ The CLI now exposes the public read-only leads endpoints under `autotouch leads`.
181
+
182
+ Use this pattern:
183
+
184
+ 1. `autotouch leads query` to page through leads, apply descriptor filters, and inspect `related_tables` / `research_summary`.
185
+ 2. `autotouch leads research` when you need actual research field values for specific lead IDs from one source table.
186
+ 3. `autotouch leads count` / `autotouch leads stats` when you need sizing before a downstream workflow.
187
+
188
+ Important contract notes:
189
+ - External callers should use `POST /api/leads/query` rather than the older `GET /api/leads` list route.
190
+ - `autotouch leads query` accepts the same descriptor payload shape as the backend:
191
+ - `filter`
192
+ - `search`
193
+ - `sort`
194
+ - `pagination`
195
+ - `scope`
196
+ - optional research context (`source_table_id`, `source_table_scope`, `related_tables_mode`)
197
+ - Research values are best treated as a follow-up call:
198
+ - `query` returns lead rows plus research linkage metadata
199
+ - `research` returns `research_data_by_id` keyed by lead id for one `source_table_id`
200
+
201
+ Examples:
202
+
203
+ ```bash
204
+ autotouch leads query \
205
+ --filter-json '{"root":{"kind":"rule","field":"status","operator":"eq","value":"new"}}' \
206
+ --limit 25
207
+ ```
208
+
209
+ ```bash
210
+ autotouch leads count \
211
+ --search "fintech" \
212
+ --scope org
213
+ ```
214
+
215
+ ```bash
216
+ autotouch leads stats \
217
+ --source-table-id <TABLE_ID> \
218
+ --source-table-scope company
219
+ ```
220
+
221
+ ```bash
222
+ autotouch leads research \
223
+ --lead-id <LEAD_ID_1> \
224
+ --lead-id <LEAD_ID_2> \
225
+ --source-table-id <TABLE_ID>
226
+ ```
227
+
228
+ ```bash
229
+ autotouch leads filters-metadata
230
+ autotouch leads research-tables
231
+ ```
232
+
148
233
  ## Bulk job status contract (authoritative run state)
149
234
 
150
235
  Use bulk jobs as the source of truth for run lifecycle:
@@ -1,12 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: autotouch-cli
3
- Version: 0.2.24
4
- Summary: Autotouch Smart Table CLI
5
- Requires-Python: >=3.9
6
- Description-Content-Type: text/markdown
7
- Requires-Dist: requests>=2.31.0
8
- Requires-Dist: python-dotenv>=1.0.0
9
-
10
1
  # Autotouch CLI Reference (`autotouch`)
11
2
 
12
3
  This page documents the installable CLI for the Smart Table developer API.
@@ -37,12 +28,13 @@ Use this order when you are orienting in the CLI:
37
28
  | verify whether a job is really done | `autotouch jobs get --job-id <JOB_ID>` |
38
29
  | create projections from JSON output | `autotouch columns projections` |
39
30
  | work with sequences/tasks | raw HTTP plus `docs/platform/external-workflows-api.md` |
31
+ | query/filter CRM leads and attached research | `autotouch leads query` then `autotouch leads research` |
40
32
 
41
33
  ### Operating model
42
34
 
43
35
  - This file is the full CLI reference and the package readme published to PyPI.
44
36
  - The installed package gives you CLI entrypoints and package metadata; do not assume there is a separate installed docs directory.
45
- - Research-table APIs are the primary CLI surface today; workflow APIs (sequences/tasks) are still HTTP-first.
37
+ - Research-table APIs and read-only leads queries are the primary CLI surface today; workflow APIs (sequences/tasks) are still HTTP-first.
46
38
  - For async operations, backend bulk-job state is authoritative; local terminal output is only a convenience layer.
47
39
  - For staged or cost-sensitive runs, estimate first and prefer filtered scopes plus `firstN` or `run-next`.
48
40
 
@@ -121,17 +113,37 @@ Notes:
121
113
  | `GET /api/tables/{table_id}/columns` | `autotouch columns list --table-id <TABLE_ID>` |
122
114
  | `POST /api/tables/{table_id}/columns` | `autotouch columns create --table-id <TABLE_ID> --data-file column.json` |
123
115
  | `PATCH /api/tables/{table_id}/columns/{column_id}` | `autotouch columns update --table-id <TABLE_ID> --column-id <COLUMN_ID> --data-file column-update.json` |
116
+ | `DELETE /api/tables/{table_id}/columns/{column_id}` | `autotouch columns delete --table-id <TABLE_ID> --column-id <COLUMN_ID> --yes` |
124
117
  | `POST /api/tables/{table_id}/columns/projections` | `autotouch columns projections --table-id <TABLE_ID> --data-file projections.json` |
125
118
  | `POST /api/tables/{table_id}/columns/{column_id}/estimate` | `autotouch columns estimate --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope all` |
126
119
  | `POST /api/tables/{table_id}/columns/{column_id}/run` | `autotouch columns run --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope all` |
127
120
  | `POST /api/tables/{table_id}/columns/{column_id}/stop` | `autotouch columns stop --table-id <TABLE_ID> --column-id <COLUMN_ID>` |
128
121
  | `GET /api/bulk-jobs` | `autotouch jobs list --table-id <TABLE_ID> --column-id <COLUMN_ID> --limit 10` |
129
122
  | `GET /api/bulk-jobs/{job_id}` | `autotouch jobs get --job-id <JOB_ID>` |
123
+ | `POST /api/leads/query` | `autotouch leads query --filter-file lead-filter.json --limit 50` |
124
+ | `POST /api/leads/query/count` | `autotouch leads count --filter-file lead-filter.json` |
125
+ | `POST /api/leads/query/stats` | `autotouch leads stats --filter-file lead-filter.json` |
126
+ | `POST /api/leads/query/research` | `autotouch leads research --lead-id <LEAD_ID> --source-table-id <TABLE_ID>` |
127
+ | `GET /api/leads/filters/metadata` | `autotouch leads filters-metadata` |
128
+ | `GET /api/leads/research-tables/available` | `autotouch leads research-tables` |
130
129
  | `GET /api/tables/{table_id}/webhook` | `autotouch webhooks get --table-id <TABLE_ID>` |
131
130
  | `POST /api/tables/{table_id}/webhook` | `autotouch webhooks rotate --table-id <TABLE_ID>` |
132
131
  | `POST /api/webhooks/tables/{table_id}/ingest` | `autotouch webhooks ingest --table-id <TABLE_ID> --records-file records.json --webhook-token <WEBHOOK_TOKEN>` |
133
132
  | `POST /api/auth/agent-bootstrap` | HTTP-only bootstrap (no direct CLI wrapper yet) |
134
133
 
134
+ ## Delete column
135
+
136
+ Delete is a hard delete:
137
+ - removes the column definition
138
+ - removes all cells stored under that column
139
+ - requires `columns:write`
140
+
141
+ CLI:
142
+
143
+ ```bash
144
+ autotouch columns delete --table-id <TABLE_ID> --column-id <COLUMN_ID> --yes
145
+ ```
146
+
135
147
  ## Workflow API coverage (sequences/tasks)
136
148
 
137
149
  Sequences/tasks APIs are supported by the backend developer-key model but do not yet have dedicated `autotouch` CLI commands.
@@ -154,6 +166,61 @@ Signature controls for sequence payloads:
154
166
  - `steps[].appendSignature` (optional, step-level) overrides signature behavior for a specific email step.
155
167
  - Signature append is deterministic at send time when enabled for the step.
156
168
 
169
+ ## Leads API coverage
170
+
171
+ The CLI now exposes the public read-only leads endpoints under `autotouch leads`.
172
+
173
+ Use this pattern:
174
+
175
+ 1. `autotouch leads query` to page through leads, apply descriptor filters, and inspect `related_tables` / `research_summary`.
176
+ 2. `autotouch leads research` when you need actual research field values for specific lead IDs from one source table.
177
+ 3. `autotouch leads count` / `autotouch leads stats` when you need sizing before a downstream workflow.
178
+
179
+ Important contract notes:
180
+ - External callers should use `POST /api/leads/query` rather than the older `GET /api/leads` list route.
181
+ - `autotouch leads query` accepts the same descriptor payload shape as the backend:
182
+ - `filter`
183
+ - `search`
184
+ - `sort`
185
+ - `pagination`
186
+ - `scope`
187
+ - optional research context (`source_table_id`, `source_table_scope`, `related_tables_mode`)
188
+ - Research values are best treated as a follow-up call:
189
+ - `query` returns lead rows plus research linkage metadata
190
+ - `research` returns `research_data_by_id` keyed by lead id for one `source_table_id`
191
+
192
+ Examples:
193
+
194
+ ```bash
195
+ autotouch leads query \
196
+ --filter-json '{"root":{"kind":"rule","field":"status","operator":"eq","value":"new"}}' \
197
+ --limit 25
198
+ ```
199
+
200
+ ```bash
201
+ autotouch leads count \
202
+ --search "fintech" \
203
+ --scope org
204
+ ```
205
+
206
+ ```bash
207
+ autotouch leads stats \
208
+ --source-table-id <TABLE_ID> \
209
+ --source-table-scope company
210
+ ```
211
+
212
+ ```bash
213
+ autotouch leads research \
214
+ --lead-id <LEAD_ID_1> \
215
+ --lead-id <LEAD_ID_2> \
216
+ --source-table-id <TABLE_ID>
217
+ ```
218
+
219
+ ```bash
220
+ autotouch leads filters-metadata
221
+ autotouch leads research-tables
222
+ ```
223
+
157
224
  ## Bulk job status contract (authoritative run state)
158
225
 
159
226
  Use bulk jobs as the source of truth for run lifecycle:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "autotouch-cli"
7
- version = "0.2.24"
7
+ version = "0.2.26"
8
8
  description = "Autotouch Smart Table CLI"
9
9
  readme = "docs/research-table/reference/autotouch-cli.md"
10
10
  requires-python = ">=3.9"
@@ -998,6 +998,198 @@ def _normalize_run_payload(args: argparse.Namespace) -> Dict[str, Any]:
998
998
  return payload
999
999
 
1000
1000
 
1001
+ def _normalize_filter_payload(filter_payload: Any) -> Any:
1002
+ if filter_payload is None:
1003
+ return None
1004
+ if isinstance(filter_payload, dict):
1005
+ return filter_payload
1006
+ print("ERROR: leads filter payload must be a JSON object", file=sys.stderr)
1007
+ sys.exit(2)
1008
+
1009
+
1010
+ def _normalize_sort_payload(sort_payload: Any) -> Any:
1011
+ if sort_payload is None:
1012
+ return None
1013
+ if isinstance(sort_payload, list):
1014
+ return sort_payload
1015
+ if isinstance(sort_payload, dict):
1016
+ return [sort_payload]
1017
+ print("ERROR: leads sort payload must be a JSON object or array", file=sys.stderr)
1018
+ sys.exit(2)
1019
+
1020
+
1021
+ def _normalize_leads_query_payload(args: argparse.Namespace) -> Dict[str, Any]:
1022
+ explicit_payload = _load_json_input(
1023
+ inline_json=getattr(args, "data_json", None),
1024
+ file_path=getattr(args, "data_file", None),
1025
+ context="data",
1026
+ default=None,
1027
+ )
1028
+ if explicit_payload is not None:
1029
+ if not isinstance(explicit_payload, dict):
1030
+ print("ERROR: leads payload must be a JSON object", file=sys.stderr)
1031
+ sys.exit(2)
1032
+ return explicit_payload
1033
+
1034
+ payload: Dict[str, Any] = {}
1035
+
1036
+ filter_payload = _normalize_filter_payload(
1037
+ _load_json_input(
1038
+ inline_json=getattr(args, "filter_json", None),
1039
+ file_path=getattr(args, "filter_file", None),
1040
+ context="filter",
1041
+ default=None,
1042
+ )
1043
+ )
1044
+ if filter_payload is not None:
1045
+ payload["filter"] = filter_payload
1046
+
1047
+ sort_payload = _normalize_sort_payload(
1048
+ _load_json_input(
1049
+ inline_json=getattr(args, "sort_json", None),
1050
+ file_path=getattr(args, "sort_file", None),
1051
+ context="sort",
1052
+ default=None,
1053
+ )
1054
+ )
1055
+ if sort_payload is not None:
1056
+ payload["sort"] = sort_payload
1057
+
1058
+ search = str(getattr(args, "search", "") or "").strip()
1059
+ if search:
1060
+ payload["search"] = search
1061
+
1062
+ pagination: Dict[str, Any] = {}
1063
+ limit = getattr(args, "limit", None)
1064
+ if limit is not None:
1065
+ pagination["limit"] = int(limit)
1066
+ cursor = str(getattr(args, "cursor", "") or "").strip()
1067
+ if cursor:
1068
+ pagination["cursor"] = cursor
1069
+ if pagination:
1070
+ payload["pagination"] = pagination
1071
+
1072
+ scope = str(getattr(args, "scope", "") or "").strip()
1073
+ if scope:
1074
+ payload["scope"] = scope
1075
+
1076
+ source_table_id = str(getattr(args, "source_table_id", "") or "").strip()
1077
+ if source_table_id:
1078
+ payload["source_table_id"] = source_table_id
1079
+
1080
+ source_table_scope = str(getattr(args, "source_table_scope", "") or "").strip()
1081
+ if source_table_scope:
1082
+ payload["source_table_scope"] = source_table_scope
1083
+
1084
+ related_tables_mode = str(getattr(args, "related_tables_mode", "") or "").strip()
1085
+ if related_tables_mode:
1086
+ payload["related_tables_mode"] = related_tables_mode
1087
+
1088
+ if bool(getattr(args, "include_research_data", False)):
1089
+ payload["include_research_data"] = True
1090
+
1091
+ if bool(getattr(args, "include_placeholder_leads", False)):
1092
+ payload["include_placeholder_leads"] = True
1093
+
1094
+ return payload
1095
+
1096
+
1097
+ def _collect_cli_string_values(raw_values: Optional[List[str]]) -> List[str]:
1098
+ values: List[str] = []
1099
+ for raw in raw_values or []:
1100
+ for piece in str(raw).replace("\n", ",").split(","):
1101
+ normalized = str(piece or "").strip()
1102
+ if normalized:
1103
+ values.append(normalized)
1104
+ return values
1105
+
1106
+
1107
+ def _parse_string_list_payload(payload: Any, *, context: str, key: Optional[str] = None) -> List[str]:
1108
+ raw_values: List[Any]
1109
+ if isinstance(payload, list):
1110
+ raw_values = payload
1111
+ elif isinstance(payload, dict) and key and isinstance(payload.get(key), list):
1112
+ raw_values = payload.get(key) or []
1113
+ else:
1114
+ suffix = f" or object with '{key}' array" if key else ""
1115
+ print(f"ERROR: {context} must be a JSON array{suffix}", file=sys.stderr)
1116
+ sys.exit(2)
1117
+
1118
+ values: List[str] = []
1119
+ for item in raw_values:
1120
+ normalized = str(item or "").strip()
1121
+ if normalized:
1122
+ values.append(normalized)
1123
+ return values
1124
+
1125
+
1126
+ def _load_string_list_input(
1127
+ *,
1128
+ cli_values: Optional[List[str]],
1129
+ inline_json: Optional[str],
1130
+ file_path: Optional[str],
1131
+ context: str,
1132
+ key: Optional[str] = None,
1133
+ ) -> List[str]:
1134
+ values = _collect_cli_string_values(cli_values)
1135
+ payload = _load_json_input(
1136
+ inline_json=inline_json,
1137
+ file_path=file_path,
1138
+ context=context,
1139
+ default=None,
1140
+ )
1141
+ if payload is not None:
1142
+ values.extend(_parse_string_list_payload(payload, context=context, key=key))
1143
+ return _dedupe_keep_order(values)
1144
+
1145
+
1146
+ def _normalize_leads_research_payload(args: argparse.Namespace) -> Dict[str, Any]:
1147
+ explicit_payload = _load_json_input(
1148
+ inline_json=getattr(args, "data_json", None),
1149
+ file_path=getattr(args, "data_file", None),
1150
+ context="data",
1151
+ default=None,
1152
+ )
1153
+ if explicit_payload is not None:
1154
+ if not isinstance(explicit_payload, dict):
1155
+ print("ERROR: leads research payload must be a JSON object", file=sys.stderr)
1156
+ sys.exit(2)
1157
+ return explicit_payload
1158
+
1159
+ lead_ids = _load_string_list_input(
1160
+ cli_values=getattr(args, "lead_id", None),
1161
+ inline_json=getattr(args, "lead_ids_json", None),
1162
+ file_path=getattr(args, "lead_ids_file", None),
1163
+ context="lead_ids",
1164
+ key="lead_ids",
1165
+ )
1166
+ if not lead_ids:
1167
+ print("ERROR: provide at least one lead id", file=sys.stderr)
1168
+ sys.exit(2)
1169
+
1170
+ source_table_id = str(getattr(args, "source_table_id", "") or "").strip()
1171
+ if not source_table_id:
1172
+ print("ERROR: --source-table-id is required", file=sys.stderr)
1173
+ sys.exit(2)
1174
+
1175
+ payload: Dict[str, Any] = {
1176
+ "lead_ids": lead_ids,
1177
+ "source_table_id": source_table_id,
1178
+ }
1179
+
1180
+ field_ids = _load_string_list_input(
1181
+ cli_values=getattr(args, "field_id", None),
1182
+ inline_json=getattr(args, "field_ids_json", None),
1183
+ file_path=getattr(args, "field_ids_file", None),
1184
+ context="field_ids",
1185
+ key="field_ids",
1186
+ )
1187
+ if field_ids:
1188
+ payload["field_ids"] = field_ids
1189
+
1190
+ return payload
1191
+
1192
+
1001
1193
  def _resolve_column_key(
1002
1194
  *,
1003
1195
  table_id: str,
@@ -3562,6 +3754,27 @@ def cmd_columns_update(args: argparse.Namespace) -> None:
3562
3754
  _print_json(data, compact=args.compact)
3563
3755
 
3564
3756
 
3757
+ def cmd_columns_delete(args: argparse.Namespace) -> None:
3758
+ if not getattr(args, "yes", False):
3759
+ print(
3760
+ "ERROR: column delete is destructive and also removes all cells for that column. Re-run with --yes to confirm.",
3761
+ file=sys.stderr,
3762
+ )
3763
+ sys.exit(2)
3764
+
3765
+ token = _resolve_token(args.token, required=True)
3766
+ data = _request_api(
3767
+ "DELETE",
3768
+ f"/api/tables/{args.table_id}/columns/{args.column_id}",
3769
+ base_url=args.base_url,
3770
+ token=token,
3771
+ use_x_api_key=args.use_x_api_key,
3772
+ timeout=args.timeout,
3773
+ verbose=args.verbose,
3774
+ )
3775
+ _print_json(data, compact=args.compact)
3776
+
3777
+
3565
3778
  def cmd_columns_projections(args: argparse.Namespace) -> None:
3566
3779
  token = _resolve_token(args.token, required=True)
3567
3780
  payload = _load_json_input(
@@ -3765,6 +3978,98 @@ def cmd_jobs_watch(args: argparse.Namespace) -> None:
3765
3978
  sys.exit(1)
3766
3979
 
3767
3980
 
3981
+ def cmd_leads_query(args: argparse.Namespace) -> None:
3982
+ token = _resolve_token(args.token, required=True)
3983
+ payload = _normalize_leads_query_payload(args)
3984
+ data = _request_api(
3985
+ "POST",
3986
+ "/api/leads/query",
3987
+ base_url=args.base_url,
3988
+ token=token,
3989
+ use_x_api_key=args.use_x_api_key,
3990
+ payload=payload,
3991
+ timeout=args.timeout,
3992
+ verbose=args.verbose,
3993
+ )
3994
+ _print_json(data, compact=args.compact)
3995
+
3996
+
3997
+ def cmd_leads_count(args: argparse.Namespace) -> None:
3998
+ token = _resolve_token(args.token, required=True)
3999
+ payload = _normalize_leads_query_payload(args)
4000
+ data = _request_api(
4001
+ "POST",
4002
+ "/api/leads/query/count",
4003
+ base_url=args.base_url,
4004
+ token=token,
4005
+ use_x_api_key=args.use_x_api_key,
4006
+ payload=payload,
4007
+ timeout=args.timeout,
4008
+ verbose=args.verbose,
4009
+ )
4010
+ _print_json(data, compact=args.compact)
4011
+
4012
+
4013
+ def cmd_leads_stats(args: argparse.Namespace) -> None:
4014
+ token = _resolve_token(args.token, required=True)
4015
+ payload = _normalize_leads_query_payload(args)
4016
+ data = _request_api(
4017
+ "POST",
4018
+ "/api/leads/query/stats",
4019
+ base_url=args.base_url,
4020
+ token=token,
4021
+ use_x_api_key=args.use_x_api_key,
4022
+ payload=payload,
4023
+ timeout=args.timeout,
4024
+ verbose=args.verbose,
4025
+ )
4026
+ _print_json(data, compact=args.compact)
4027
+
4028
+
4029
+ def cmd_leads_research(args: argparse.Namespace) -> None:
4030
+ token = _resolve_token(args.token, required=True)
4031
+ payload = _normalize_leads_research_payload(args)
4032
+ data = _request_api(
4033
+ "POST",
4034
+ "/api/leads/query/research",
4035
+ base_url=args.base_url,
4036
+ token=token,
4037
+ use_x_api_key=args.use_x_api_key,
4038
+ payload=payload,
4039
+ timeout=args.timeout,
4040
+ verbose=args.verbose,
4041
+ )
4042
+ _print_json(data, compact=args.compact)
4043
+
4044
+
4045
+ def cmd_leads_filters_metadata(args: argparse.Namespace) -> None:
4046
+ token = _resolve_token(args.token, required=True)
4047
+ data = _request_api(
4048
+ "GET",
4049
+ "/api/leads/filters/metadata",
4050
+ base_url=args.base_url,
4051
+ token=token,
4052
+ use_x_api_key=args.use_x_api_key,
4053
+ timeout=args.timeout,
4054
+ verbose=args.verbose,
4055
+ )
4056
+ _print_json(data, compact=args.compact)
4057
+
4058
+
4059
+ def cmd_leads_research_tables(args: argparse.Namespace) -> None:
4060
+ token = _resolve_token(args.token, required=True)
4061
+ data = _request_api(
4062
+ "GET",
4063
+ "/api/leads/research-tables/available",
4064
+ base_url=args.base_url,
4065
+ token=token,
4066
+ use_x_api_key=args.use_x_api_key,
4067
+ timeout=args.timeout,
4068
+ verbose=args.verbose,
4069
+ )
4070
+ _print_json(data, compact=args.compact)
4071
+
4072
+
3768
4073
  def cmd_webhooks_get(args: argparse.Namespace) -> None:
3769
4074
  token = _resolve_token(args.token, required=True)
3770
4075
  data = _request_api(
@@ -4156,6 +4461,60 @@ def _add_run_execution_arguments(parser: argparse.ArgumentParser) -> None:
4156
4461
  parser.add_argument("--fail-on-partial", action="store_true", help="Exit non-zero when final status is partial")
4157
4462
 
4158
4463
 
4464
+ def _add_leads_query_arguments(parser: argparse.ArgumentParser) -> None:
4465
+ parser.add_argument("--data-json", help="Explicit leads query payload JSON")
4466
+ parser.add_argument("--data-file", help="Path to leads query payload JSON file")
4467
+ parser.add_argument("--filter-json", help="Filter descriptor JSON object")
4468
+ parser.add_argument("--filter-file", help="Path to filter descriptor JSON file")
4469
+ parser.add_argument("--sort-json", help="Sort spec JSON object/array")
4470
+ parser.add_argument("--sort-file", help="Path to sort spec JSON file")
4471
+ parser.add_argument("--search", help="Free-text lead search")
4472
+ parser.add_argument("--limit", type=int, help="Pagination limit")
4473
+ parser.add_argument("--cursor", help="Pagination cursor")
4474
+ parser.add_argument("--scope", choices=["org", "user"], help="Lead scope")
4475
+ parser.add_argument("--source-table-id", help="Research table id used for scope/research context")
4476
+ parser.add_argument(
4477
+ "--source-table-scope",
4478
+ choices=["company", "lead"],
4479
+ help="Research scope resolution mode",
4480
+ )
4481
+ parser.add_argument(
4482
+ "--related-tables-mode",
4483
+ choices=["linked", "direct", "none"],
4484
+ help="How related_tables should be populated",
4485
+ )
4486
+ parser.add_argument(
4487
+ "--include-research-data",
4488
+ action="store_true",
4489
+ help="Inline research_data when --source-table-id is provided",
4490
+ )
4491
+ parser.add_argument(
4492
+ "--include-placeholder-leads",
4493
+ action="store_true",
4494
+ help="Include placeholder/incomplete lead records",
4495
+ )
4496
+
4497
+
4498
+ def _add_leads_research_arguments(parser: argparse.ArgumentParser) -> None:
4499
+ parser.add_argument("--data-json", help="Explicit leads research payload JSON")
4500
+ parser.add_argument("--data-file", help="Path to leads research payload JSON file")
4501
+ parser.add_argument(
4502
+ "--lead-id",
4503
+ action="append",
4504
+ help="Lead id (repeatable; comma-separated also supported)",
4505
+ )
4506
+ parser.add_argument("--lead-ids-json", help="JSON array or object with lead_ids[]")
4507
+ parser.add_argument("--lead-ids-file", help="Path to JSON file with lead_ids[]")
4508
+ parser.add_argument("--source-table-id", help="Research table id")
4509
+ parser.add_argument(
4510
+ "--field-id",
4511
+ action="append",
4512
+ help="Research field/column id to include (repeatable; comma-separated also supported)",
4513
+ )
4514
+ parser.add_argument("--field-ids-json", help="JSON array or object with field_ids[]")
4515
+ parser.add_argument("--field-ids-file", help="Path to JSON file with field_ids[]")
4516
+
4517
+
4159
4518
  def build_parser() -> argparse.ArgumentParser:
4160
4519
  p = argparse.ArgumentParser(prog="autotouch", description="Smart Table CLI")
4161
4520
  sub = p.add_subparsers(dest="cmd", required=True)
@@ -4461,6 +4820,13 @@ def build_parser() -> argparse.ArgumentParser:
4461
4820
  _add_api_common_arguments(pcu)
4462
4821
  pcu.set_defaults(func=cmd_columns_update)
4463
4822
 
4823
+ pcd = col_sub.add_parser("delete", help="Delete a column and all of its cells")
4824
+ pcd.add_argument("--table-id", required=True)
4825
+ pcd.add_argument("--column-id", required=True)
4826
+ pcd.add_argument("--yes", action="store_true", help="Required confirmation flag for destructive delete")
4827
+ _add_api_common_arguments(pcd)
4828
+ pcd.set_defaults(func=cmd_columns_delete)
4829
+
4464
4830
  pcpj = col_sub.add_parser("projections", help="Create JSON projection columns")
4465
4831
  pcpj.add_argument("--table-id", required=True)
4466
4832
  pcpj.add_argument("--data-json", help="CreateProjectionsRequest payload JSON")
@@ -4515,6 +4881,38 @@ def build_parser() -> argparse.ArgumentParser:
4515
4881
  pcre.add_argument("--compact", action="store_true", help="Print compact JSON")
4516
4882
  pcre.set_defaults(func=cmd_columns_recipe)
4517
4883
 
4884
+ # leads
4885
+ pl = sub.add_parser("leads", help="Read-only leads query operations")
4886
+ leads_sub = pl.add_subparsers(dest="leads_cmd", required=True)
4887
+
4888
+ plq = leads_sub.add_parser("query", help="Query/filter leads with descriptor payloads")
4889
+ _add_leads_query_arguments(plq)
4890
+ _add_api_common_arguments(plq)
4891
+ plq.set_defaults(func=cmd_leads_query)
4892
+
4893
+ plc = leads_sub.add_parser("count", help="Count leads matching a query payload")
4894
+ _add_leads_query_arguments(plc)
4895
+ _add_api_common_arguments(plc)
4896
+ plc.set_defaults(func=cmd_leads_count)
4897
+
4898
+ pls = leads_sub.add_parser("stats", help="Aggregate stats for leads matching a query payload")
4899
+ _add_leads_query_arguments(pls)
4900
+ _add_api_common_arguments(pls)
4901
+ pls.set_defaults(func=cmd_leads_stats)
4902
+
4903
+ plr = leads_sub.add_parser("research", help="Fetch research row data for specific lead ids")
4904
+ _add_leads_research_arguments(plr)
4905
+ _add_api_common_arguments(plr)
4906
+ plr.set_defaults(func=cmd_leads_research)
4907
+
4908
+ plf = leads_sub.add_parser("filters-metadata", help="List filterable lead fields and research columns")
4909
+ _add_api_common_arguments(plf)
4910
+ plf.set_defaults(func=cmd_leads_filters_metadata)
4911
+
4912
+ plrt = leads_sub.add_parser("research-tables", help="List research tables available for lead lookups")
4913
+ _add_api_common_arguments(plrt)
4914
+ plrt.set_defaults(func=cmd_leads_research_tables)
4915
+
4518
4916
  # jobs
4519
4917
  pj = sub.add_parser("jobs", help="Bulk job operations")
4520
4918
  jobs_sub = pj.add_subparsers(dest="jobs_cmd", required=True)
File without changes