autotouch-cli 0.2.20__tar.gz → 0.2.22__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.20 → autotouch_cli-0.2.22}/PKG-INFO +129 -5
  2. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/autotouch_cli.egg-info/PKG-INFO +129 -5
  3. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/autotouch_cli.egg-info/SOURCES.txt +1 -0
  4. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/docs/research-table/reference/autotouch-cli-pypi.md +128 -4
  5. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/pyproject.toml +1 -1
  6. autotouch_cli-0.2.22/scripts/migrations/20260305_force_formatter_autorun_on_source_update.py +98 -0
  7. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/smart_table_cli.py +937 -1
  8. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/autotouch_cli.egg-info/dependency_links.txt +0 -0
  9. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/autotouch_cli.egg-info/entry_points.txt +0 -0
  10. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/autotouch_cli.egg-info/requires.txt +0 -0
  11. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/autotouch_cli.egg-info/top_level.txt +0 -0
  12. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/__init__.py +0 -0
  13. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/add_column_unique_index.py +0 -0
  14. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/attach_csv_import_leads_to_research_table.py +0 -0
  15. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/bundle_sequences_backend.py +0 -0
  16. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/check_agent_traces.py +0 -0
  17. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/check_column_mode.py +0 -0
  18. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/exit_terminal_leads_from_sequences.py +0 -0
  19. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/fetch_lead.py +0 -0
  20. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/fix_lead_titles_from_csv.py +0 -0
  21. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250106_add_column_position.py +0 -0
  22. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250108_fix_legacy_column_fields.py +0 -0
  23. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250109_add_user_fields_to_tables.py +0 -0
  24. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250117_add_call_logs_webhook_indexes.py +0 -0
  25. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250117_rename_call_logs_collection.py +0 -0
  26. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250119_create_leads_unique_email_index.py +0 -0
  27. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250123_add_filter_indexes.py +0 -0
  28. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250123_add_llm_responses_collection.py +0 -0
  29. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250128_migrate_user_ids_to_objectid.py +0 -0
  30. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250208_backfill_task_research_values.py +0 -0
  31. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250604_add_origin_indexes.py +0 -0
  32. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250608_cleanup_agent_metadata.py +0 -0
  33. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250608_rename_agent_metadata_to_metadata.py +0 -0
  34. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250922_add_activity_indexes.py +0 -0
  35. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250926_migrate_single_to_arrays.py +0 -0
  36. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250928_add_missing_timestamp_fields.py +0 -0
  37. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250929_add_task_join_indexes.py +0 -0
  38. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250929_add_task_join_indexes_safe.py +0 -0
  39. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20250929_create_shared_phone_cache.py +0 -0
  40. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20251007_add_rows_position_id_index.py +0 -0
  41. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20251109_add_ttl_for_llm_and_preview_traces.py +0 -0
  42. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20260113_normalize_table_filter_operators.py +0 -0
  43. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20260113_set_user_permissions_user_admin.py +0 -0
  44. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20260204_sync_lead_owner_from_tasks.py +0 -0
  45. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/20260303_add_webhook_subscription_collections.py +0 -0
  46. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/migrate_org_user_credits.py +0 -0
  47. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/set_default_lead_status.py +0 -0
  48. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/migrations/update_lead_owner_from_tasks.py +0 -0
  49. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/reassign_sequence_owner.py +0 -0
  50. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/run_sidecar_orchestrator_demo.py +0 -0
  51. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/test_crm_company_policy.py +0 -0
  52. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/test_sequences_instantly_e2e.py +0 -0
  53. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/test_sequences_personal_e2e.py +0 -0
  54. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/test_task_error_logger.py +0 -0
  55. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/scripts/verify_azurite_voicemail.py +0 -0
  56. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/setup.cfg +0 -0
  57. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/tests/test_contactout_custom.py +0 -0
  58. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/tests/test_contactout_integration.py +0 -0
  59. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/tests/test_contactout_multi_titles.py +0 -0
  60. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/tests/test_contactout_pipeline.py +0 -0
  61. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/tests/test_contactout_simple.py +0 -0
  62. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/tests/test_contactout_v2_bulk.py +0 -0
  63. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/tests/test_lead_required_fields.py +0 -0
  64. {autotouch_cli-0.2.20 → autotouch_cli-0.2.22}/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.20
3
+ Version: 0.2.22
4
4
  Summary: Autotouch Smart Table CLI
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -72,12 +72,12 @@ Reference:
72
72
  # 1) Verify target table exists and copy the exact id
73
73
  autotouch tables list --view-mode org --output human
74
74
 
75
- # 2) Parse CSV only (no write)
75
+ # 2) Validate server-side CSV parse/shape (no write)
76
76
  autotouch rows import-csv \
77
77
  --table-id <TABLE_ID> \
78
78
  --confirm-table-id <TABLE_ID> \
79
79
  --file contacts.csv \
80
- --dry-run \
80
+ --validate-only \
81
81
  --output json
82
82
  ```
83
83
 
@@ -101,7 +101,56 @@ Notes:
101
101
  - `--transport optimized` is default (`/import-optimized`).
102
102
  - `--wait` polls import status to terminal state.
103
103
  - `--checkpoint-file` stores state and blocks accidental duplicate re-imports.
104
+ - `--validate-only` validates CSV parse/shape on server without writing rows.
105
+ - Blacklist controls on optimized import:
106
+ - company-domain filtering is ON by default
107
+ - email filtering is OFF by default
108
+ - disable company filtering with `--no-check-company-blacklist`
109
+ - enable email filtering with `--check-email-blacklist`
110
+ - Safety assertions:
111
+ - `--expected-rows <N>`
112
+ - `--require-columns col_a,col_b`
113
+ - `--duplicate-key col_a,col_b`
114
+ - `--require-non-empty key:ratio`
115
+ - For async imports with `--wait`, assertions are rechecked postflight via task manifest verification.
104
116
  - Use `--allow-reimport` if you intentionally want to run the same file again.
117
+ - Import responses include `blacklist_summary` with company/email `skipped_count` and `enforced` flags.
118
+
119
+ Example enabling both company and email enforcement:
120
+
121
+ ```bash
122
+ autotouch rows import-csv \
123
+ --table-id <TABLE_ID> \
124
+ --confirm-table-id <TABLE_ID> \
125
+ --file contacts.csv \
126
+ --checkpoint-file .autotouch-import.json \
127
+ --check-email-blacklist \
128
+ --wait \
129
+ --output json
130
+ ```
131
+
132
+ Manage blacklist entries (native CLI, admin identity required):
133
+
134
+ ```bash
135
+ # List current entries
136
+ autotouch blacklist list --type-filter all --limit 100
137
+
138
+ # Add entries
139
+ autotouch blacklist add --type domain --value competitor.com --reason "Do not contact"
140
+ autotouch blacklist add --type email --value blocked@example.com --reason "Unsubscribed"
141
+
142
+ # Bulk-import entries from CSV/TXT
143
+ autotouch blacklist import --file blacklist.csv
144
+
145
+ # Check/filter email sets (auto-chunked for large lists)
146
+ autotouch blacklist check --emails-file recipients.csv --emails-column email --summary-only
147
+ autotouch blacklist filter --emails-file recipients.csv --emails-column email --summary-only
148
+ ```
149
+
150
+ Recommended timing:
151
+ - Add suppressions early (unsubscribed addresses, existing customers, competitors, and clear non-ICP domains).
152
+ - Before billable enrichments (`llm_enrichment`, `email_finder`, `phone_finder`), run blacklist filtering and enrich only clean candidates.
153
+ - Run one final blacklist check/filter before outreach execution.
105
154
 
106
155
  ## Import modes
107
156
 
@@ -133,6 +182,7 @@ Auto-run is configured per column (`autoRun`), not per table.
133
182
  Important:
134
183
  - Insert events do not run `onSourceUpdate` columns.
135
184
  - Imports may queue auto-run dispatch evaluation, but only columns whose `autoRun` policy matches the event are queued.
185
+ - Formatter columns are normalized server-side to `onSourceUpdate` (attempted `never`/`onInsert` values are overridden).
136
186
 
137
187
  ## Create a column
138
188
 
@@ -157,6 +207,7 @@ Notes:
157
207
  - `add_to_crm` is optional and non-billable.
158
208
  - Email/phone enrichment does not require creating/running `add_to_crm`.
159
209
  - For `add_to_crm`, required mapping keys are `linkedinUrl` and `companyDomain`.
210
+ - Formatter formulas must use row references (`row['first_name']`, `row.last_name`), not bare template placeholders like ``${first_name}``.
160
211
  - `sync_to_table` supports both:
161
212
  - single destination: `destinationTableId` + `columnMappings`
162
213
  - router mode: `routes[]` (first matching route wins)
@@ -164,6 +215,7 @@ Notes:
164
215
  - `sequenceId`
165
216
  - `sourceLeadColumn` pointing to a lead-id producing column (`add_to_crm` or `lead_finder` output)
166
217
  - auto-attaches research context defaults during enrollment (`source_table_id`, plus optional table name); favorite fields resolve from current starred columns when explicit `fieldIds` are not provided
218
+ - star/favorite high-signal fields so call-sidecar context stays quick to scan and AI draft context stays focused
167
219
 
168
220
  If you create custom `llm_enrichment` schemas:
169
221
  - Use strict field-map schema shape (no root `type/properties` wrapper).
@@ -209,6 +261,65 @@ This keeps quickstart short while documenting the full strategy:
209
261
  - one-person-per-row output contract,
210
262
  - JSON split + downstream enrichment chaining.
211
263
 
264
+ ## First-principles shape: intent + context
265
+
266
+ For most workflows, useful output combines:
267
+ - context: source evidence (what happened / what was observed)
268
+ - intent: interpretation signal (what to prioritize / do next)
269
+
270
+ Design guidance:
271
+ - preserve full-fidelity context in at least one raw field (for example full post/body text)
272
+ - default behavior for agents: write full context/content, not summaries or truncation
273
+ - only truncate/summarize when there is a hard limit (storage/model/provider/payload), and explicitly mark truncation
274
+ - keep intent separate from raw context so downstream logic can change without losing source data
275
+ - for calling workflows, favorite/star key fields so users can access the most relevant context quickly in sidecar views
276
+ - for AI-generated emails/copy, include both intent and full context in imports so drafts are grounded, not generic
277
+ - keep one entity per row and dedupe on a stable identity key
278
+ - keep snippets/summaries optional and derived from raw context, never a replacement for it
279
+ - normalize intent labels if you need deterministic automation
280
+
281
+ Optional field pattern (adapt to your workflow):
282
+ - `post_content` or `context_raw`: full long-form context
283
+ - `context_url`: source URL
284
+ - `context_timestamp`: recency marker
285
+ - `intent_label`: normalized intent category
286
+ - `intent_reason`: concise rationale
287
+
288
+ CSV import note for context fields:
289
+ - long text fields like `post_content` can contain newlines and commas
290
+ - that is valid CSV when fields are properly quoted
291
+ - always run `rows import-csv --validate-only` (or `--dry-run`) before write
292
+ - for strict guardrails, add assertions: `--expected-rows`, `--require-columns`, `--duplicate-key`, `--require-non-empty`
293
+ - import does not intentionally truncate text values; very large single-cell payloads are still bounded by Mongo document limits
294
+
295
+ Verification + rollback commands:
296
+ - `autotouch rows import-verify --table-id <TABLE_ID> --task-id <TASK_ID> --expected-rows <N>`
297
+ - `autotouch rows import-rollback --table-id <TABLE_ID> --task-id <TASK_ID>`
298
+
299
+ Multiline `post_content` examples:
300
+
301
+ ```csv
302
+ # bad (unquoted multiline content breaks row shape)
303
+ first_name,linkedin_url,post_content
304
+ Ada,https://linkedin.com/in/ada,Line 1
305
+ Line 2
306
+ ```
307
+
308
+ ```csv
309
+ # good (quoted multiline content is valid CSV)
310
+ first_name,linkedin_url,post_content
311
+ Ada,https://linkedin.com/in/ada,"Line 1
312
+ Line 2"
313
+ ```
314
+
315
+ Do not re-import blind (recovery flow):
316
+ - stop and keep the original `task_id`
317
+ - inspect status: `autotouch rows import-status --table-id <TABLE_ID> --task-id <TASK_ID>`
318
+ - prove postflight: `autotouch rows import-verify --table-id <TABLE_ID> --task-id <TASK_ID> ...assertions...`
319
+ - if verification fails, preview rollback: `autotouch rows import-rollback --table-id <TABLE_ID> --task-id <TASK_ID> --dry-run`
320
+ - then rollback: `autotouch rows import-rollback --table-id <TABLE_ID> --task-id <TASK_ID>`
321
+ - fix CSV quoting/mapping and run `--validate-only` before any new import
322
+
212
323
  ## Sequences/Tasks workflow APIs (current CLI coverage)
213
324
 
214
325
  The installable `autotouch` CLI currently provides first-class commands for research-table APIs.
@@ -452,6 +563,15 @@ autotouch columns run-next \
452
563
  --wait
453
564
  ```
454
565
 
566
+ Blacklist gate (recommended before billable runs):
567
+
568
+ ```bash
569
+ autotouch blacklist filter \
570
+ --emails-file candidates.csv \
571
+ --emails-column work_email \
572
+ --output json
573
+ ```
574
+
455
575
  ## Job truth contract (agent-safe)
456
576
 
457
577
  - Treat a run as started only when you receive `job_id`.
@@ -507,12 +627,16 @@ If either failure status appears, treat the run as unconfirmed/inconsistent, ver
507
627
  - Re-run with same `--checkpoint-file` and `--wait`.
508
628
  - CLI can resume/poll an in-flight task from checkpoint state.
509
629
 
510
- 4. Run queued but job id missing from local output:
630
+ 4. Need proof before saying "fixed":
631
+ - Run `autotouch rows import-verify --table-id <TABLE_ID> --task-id <TASK_ID> ...assertions...`
632
+ - Treat `passed=false` as a failed import outcome.
633
+
634
+ 5. Run queued but job id missing from local output:
511
635
  - Recover from backend history:
512
636
  - `autotouch jobs list --table-id <TABLE_ID> --column-id <COLUMN_ID> --limit 5 --output json`
513
637
  - Then poll the returned `job_id` with `autotouch jobs get` / `autotouch jobs watch`.
514
638
 
515
- 5. Need to stop a running column job:
639
+ 6. Need to stop a running column job:
516
640
 
517
641
  ```bash
518
642
  autotouch columns stop --table-id <TABLE_ID> --column-id <COLUMN_ID>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autotouch-cli
3
- Version: 0.2.20
3
+ Version: 0.2.22
4
4
  Summary: Autotouch Smart Table CLI
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -72,12 +72,12 @@ Reference:
72
72
  # 1) Verify target table exists and copy the exact id
73
73
  autotouch tables list --view-mode org --output human
74
74
 
75
- # 2) Parse CSV only (no write)
75
+ # 2) Validate server-side CSV parse/shape (no write)
76
76
  autotouch rows import-csv \
77
77
  --table-id <TABLE_ID> \
78
78
  --confirm-table-id <TABLE_ID> \
79
79
  --file contacts.csv \
80
- --dry-run \
80
+ --validate-only \
81
81
  --output json
82
82
  ```
83
83
 
@@ -101,7 +101,56 @@ Notes:
101
101
  - `--transport optimized` is default (`/import-optimized`).
102
102
  - `--wait` polls import status to terminal state.
103
103
  - `--checkpoint-file` stores state and blocks accidental duplicate re-imports.
104
+ - `--validate-only` validates CSV parse/shape on server without writing rows.
105
+ - Blacklist controls on optimized import:
106
+ - company-domain filtering is ON by default
107
+ - email filtering is OFF by default
108
+ - disable company filtering with `--no-check-company-blacklist`
109
+ - enable email filtering with `--check-email-blacklist`
110
+ - Safety assertions:
111
+ - `--expected-rows <N>`
112
+ - `--require-columns col_a,col_b`
113
+ - `--duplicate-key col_a,col_b`
114
+ - `--require-non-empty key:ratio`
115
+ - For async imports with `--wait`, assertions are rechecked postflight via task manifest verification.
104
116
  - Use `--allow-reimport` if you intentionally want to run the same file again.
117
+ - Import responses include `blacklist_summary` with company/email `skipped_count` and `enforced` flags.
118
+
119
+ Example enabling both company and email enforcement:
120
+
121
+ ```bash
122
+ autotouch rows import-csv \
123
+ --table-id <TABLE_ID> \
124
+ --confirm-table-id <TABLE_ID> \
125
+ --file contacts.csv \
126
+ --checkpoint-file .autotouch-import.json \
127
+ --check-email-blacklist \
128
+ --wait \
129
+ --output json
130
+ ```
131
+
132
+ Manage blacklist entries (native CLI, admin identity required):
133
+
134
+ ```bash
135
+ # List current entries
136
+ autotouch blacklist list --type-filter all --limit 100
137
+
138
+ # Add entries
139
+ autotouch blacklist add --type domain --value competitor.com --reason "Do not contact"
140
+ autotouch blacklist add --type email --value blocked@example.com --reason "Unsubscribed"
141
+
142
+ # Bulk-import entries from CSV/TXT
143
+ autotouch blacklist import --file blacklist.csv
144
+
145
+ # Check/filter email sets (auto-chunked for large lists)
146
+ autotouch blacklist check --emails-file recipients.csv --emails-column email --summary-only
147
+ autotouch blacklist filter --emails-file recipients.csv --emails-column email --summary-only
148
+ ```
149
+
150
+ Recommended timing:
151
+ - Add suppressions early (unsubscribed addresses, existing customers, competitors, and clear non-ICP domains).
152
+ - Before billable enrichments (`llm_enrichment`, `email_finder`, `phone_finder`), run blacklist filtering and enrich only clean candidates.
153
+ - Run one final blacklist check/filter before outreach execution.
105
154
 
106
155
  ## Import modes
107
156
 
@@ -133,6 +182,7 @@ Auto-run is configured per column (`autoRun`), not per table.
133
182
  Important:
134
183
  - Insert events do not run `onSourceUpdate` columns.
135
184
  - Imports may queue auto-run dispatch evaluation, but only columns whose `autoRun` policy matches the event are queued.
185
+ - Formatter columns are normalized server-side to `onSourceUpdate` (attempted `never`/`onInsert` values are overridden).
136
186
 
137
187
  ## Create a column
138
188
 
@@ -157,6 +207,7 @@ Notes:
157
207
  - `add_to_crm` is optional and non-billable.
158
208
  - Email/phone enrichment does not require creating/running `add_to_crm`.
159
209
  - For `add_to_crm`, required mapping keys are `linkedinUrl` and `companyDomain`.
210
+ - Formatter formulas must use row references (`row['first_name']`, `row.last_name`), not bare template placeholders like ``${first_name}``.
160
211
  - `sync_to_table` supports both:
161
212
  - single destination: `destinationTableId` + `columnMappings`
162
213
  - router mode: `routes[]` (first matching route wins)
@@ -164,6 +215,7 @@ Notes:
164
215
  - `sequenceId`
165
216
  - `sourceLeadColumn` pointing to a lead-id producing column (`add_to_crm` or `lead_finder` output)
166
217
  - auto-attaches research context defaults during enrollment (`source_table_id`, plus optional table name); favorite fields resolve from current starred columns when explicit `fieldIds` are not provided
218
+ - star/favorite high-signal fields so call-sidecar context stays quick to scan and AI draft context stays focused
167
219
 
168
220
  If you create custom `llm_enrichment` schemas:
169
221
  - Use strict field-map schema shape (no root `type/properties` wrapper).
@@ -209,6 +261,65 @@ This keeps quickstart short while documenting the full strategy:
209
261
  - one-person-per-row output contract,
210
262
  - JSON split + downstream enrichment chaining.
211
263
 
264
+ ## First-principles shape: intent + context
265
+
266
+ For most workflows, useful output combines:
267
+ - context: source evidence (what happened / what was observed)
268
+ - intent: interpretation signal (what to prioritize / do next)
269
+
270
+ Design guidance:
271
+ - preserve full-fidelity context in at least one raw field (for example full post/body text)
272
+ - default behavior for agents: write full context/content, not summaries or truncation
273
+ - only truncate/summarize when there is a hard limit (storage/model/provider/payload), and explicitly mark truncation
274
+ - keep intent separate from raw context so downstream logic can change without losing source data
275
+ - for calling workflows, favorite/star key fields so users can access the most relevant context quickly in sidecar views
276
+ - for AI-generated emails/copy, include both intent and full context in imports so drafts are grounded, not generic
277
+ - keep one entity per row and dedupe on a stable identity key
278
+ - keep snippets/summaries optional and derived from raw context, never a replacement for it
279
+ - normalize intent labels if you need deterministic automation
280
+
281
+ Optional field pattern (adapt to your workflow):
282
+ - `post_content` or `context_raw`: full long-form context
283
+ - `context_url`: source URL
284
+ - `context_timestamp`: recency marker
285
+ - `intent_label`: normalized intent category
286
+ - `intent_reason`: concise rationale
287
+
288
+ CSV import note for context fields:
289
+ - long text fields like `post_content` can contain newlines and commas
290
+ - that is valid CSV when fields are properly quoted
291
+ - always run `rows import-csv --validate-only` (or `--dry-run`) before write
292
+ - for strict guardrails, add assertions: `--expected-rows`, `--require-columns`, `--duplicate-key`, `--require-non-empty`
293
+ - import does not intentionally truncate text values; very large single-cell payloads are still bounded by Mongo document limits
294
+
295
+ Verification + rollback commands:
296
+ - `autotouch rows import-verify --table-id <TABLE_ID> --task-id <TASK_ID> --expected-rows <N>`
297
+ - `autotouch rows import-rollback --table-id <TABLE_ID> --task-id <TASK_ID>`
298
+
299
+ Multiline `post_content` examples:
300
+
301
+ ```csv
302
+ # bad (unquoted multiline content breaks row shape)
303
+ first_name,linkedin_url,post_content
304
+ Ada,https://linkedin.com/in/ada,Line 1
305
+ Line 2
306
+ ```
307
+
308
+ ```csv
309
+ # good (quoted multiline content is valid CSV)
310
+ first_name,linkedin_url,post_content
311
+ Ada,https://linkedin.com/in/ada,"Line 1
312
+ Line 2"
313
+ ```
314
+
315
+ Do not re-import blind (recovery flow):
316
+ - stop and keep the original `task_id`
317
+ - inspect status: `autotouch rows import-status --table-id <TABLE_ID> --task-id <TASK_ID>`
318
+ - prove postflight: `autotouch rows import-verify --table-id <TABLE_ID> --task-id <TASK_ID> ...assertions...`
319
+ - if verification fails, preview rollback: `autotouch rows import-rollback --table-id <TABLE_ID> --task-id <TASK_ID> --dry-run`
320
+ - then rollback: `autotouch rows import-rollback --table-id <TABLE_ID> --task-id <TASK_ID>`
321
+ - fix CSV quoting/mapping and run `--validate-only` before any new import
322
+
212
323
  ## Sequences/Tasks workflow APIs (current CLI coverage)
213
324
 
214
325
  The installable `autotouch` CLI currently provides first-class commands for research-table APIs.
@@ -452,6 +563,15 @@ autotouch columns run-next \
452
563
  --wait
453
564
  ```
454
565
 
566
+ Blacklist gate (recommended before billable runs):
567
+
568
+ ```bash
569
+ autotouch blacklist filter \
570
+ --emails-file candidates.csv \
571
+ --emails-column work_email \
572
+ --output json
573
+ ```
574
+
455
575
  ## Job truth contract (agent-safe)
456
576
 
457
577
  - Treat a run as started only when you receive `job_id`.
@@ -507,12 +627,16 @@ If either failure status appears, treat the run as unconfirmed/inconsistent, ver
507
627
  - Re-run with same `--checkpoint-file` and `--wait`.
508
628
  - CLI can resume/poll an in-flight task from checkpoint state.
509
629
 
510
- 4. Run queued but job id missing from local output:
630
+ 4. Need proof before saying "fixed":
631
+ - Run `autotouch rows import-verify --table-id <TABLE_ID> --task-id <TASK_ID> ...assertions...`
632
+ - Treat `passed=false` as a failed import outcome.
633
+
634
+ 5. Run queued but job id missing from local output:
511
635
  - Recover from backend history:
512
636
  - `autotouch jobs list --table-id <TABLE_ID> --column-id <COLUMN_ID> --limit 5 --output json`
513
637
  - Then poll the returned `job_id` with `autotouch jobs get` / `autotouch jobs watch`.
514
638
 
515
- 5. Need to stop a running column job:
639
+ 6. Need to stop a running column job:
516
640
 
517
641
  ```bash
518
642
  autotouch columns stop --table-id <TABLE_ID> --column-id <COLUMN_ID>
@@ -48,6 +48,7 @@ scripts/migrations/20260113_normalize_table_filter_operators.py
48
48
  scripts/migrations/20260113_set_user_permissions_user_admin.py
49
49
  scripts/migrations/20260204_sync_lead_owner_from_tasks.py
50
50
  scripts/migrations/20260303_add_webhook_subscription_collections.py
51
+ scripts/migrations/20260305_force_formatter_autorun_on_source_update.py
51
52
  scripts/migrations/migrate_org_user_credits.py
52
53
  scripts/migrations/set_default_lead_status.py
53
54
  scripts/migrations/update_lead_owner_from_tasks.py
@@ -63,12 +63,12 @@ Reference:
63
63
  # 1) Verify target table exists and copy the exact id
64
64
  autotouch tables list --view-mode org --output human
65
65
 
66
- # 2) Parse CSV only (no write)
66
+ # 2) Validate server-side CSV parse/shape (no write)
67
67
  autotouch rows import-csv \
68
68
  --table-id <TABLE_ID> \
69
69
  --confirm-table-id <TABLE_ID> \
70
70
  --file contacts.csv \
71
- --dry-run \
71
+ --validate-only \
72
72
  --output json
73
73
  ```
74
74
 
@@ -92,7 +92,56 @@ Notes:
92
92
  - `--transport optimized` is default (`/import-optimized`).
93
93
  - `--wait` polls import status to terminal state.
94
94
  - `--checkpoint-file` stores state and blocks accidental duplicate re-imports.
95
+ - `--validate-only` validates CSV parse/shape on server without writing rows.
96
+ - Blacklist controls on optimized import:
97
+ - company-domain filtering is ON by default
98
+ - email filtering is OFF by default
99
+ - disable company filtering with `--no-check-company-blacklist`
100
+ - enable email filtering with `--check-email-blacklist`
101
+ - Safety assertions:
102
+ - `--expected-rows <N>`
103
+ - `--require-columns col_a,col_b`
104
+ - `--duplicate-key col_a,col_b`
105
+ - `--require-non-empty key:ratio`
106
+ - For async imports with `--wait`, assertions are rechecked postflight via task manifest verification.
95
107
  - Use `--allow-reimport` if you intentionally want to run the same file again.
108
+ - Import responses include `blacklist_summary` with company/email `skipped_count` and `enforced` flags.
109
+
110
+ Example enabling both company and email enforcement:
111
+
112
+ ```bash
113
+ autotouch rows import-csv \
114
+ --table-id <TABLE_ID> \
115
+ --confirm-table-id <TABLE_ID> \
116
+ --file contacts.csv \
117
+ --checkpoint-file .autotouch-import.json \
118
+ --check-email-blacklist \
119
+ --wait \
120
+ --output json
121
+ ```
122
+
123
+ Manage blacklist entries (native CLI, admin identity required):
124
+
125
+ ```bash
126
+ # List current entries
127
+ autotouch blacklist list --type-filter all --limit 100
128
+
129
+ # Add entries
130
+ autotouch blacklist add --type domain --value competitor.com --reason "Do not contact"
131
+ autotouch blacklist add --type email --value blocked@example.com --reason "Unsubscribed"
132
+
133
+ # Bulk-import entries from CSV/TXT
134
+ autotouch blacklist import --file blacklist.csv
135
+
136
+ # Check/filter email sets (auto-chunked for large lists)
137
+ autotouch blacklist check --emails-file recipients.csv --emails-column email --summary-only
138
+ autotouch blacklist filter --emails-file recipients.csv --emails-column email --summary-only
139
+ ```
140
+
141
+ Recommended timing:
142
+ - Add suppressions early (unsubscribed addresses, existing customers, competitors, and clear non-ICP domains).
143
+ - Before billable enrichments (`llm_enrichment`, `email_finder`, `phone_finder`), run blacklist filtering and enrich only clean candidates.
144
+ - Run one final blacklist check/filter before outreach execution.
96
145
 
97
146
  ## Import modes
98
147
 
@@ -124,6 +173,7 @@ Auto-run is configured per column (`autoRun`), not per table.
124
173
  Important:
125
174
  - Insert events do not run `onSourceUpdate` columns.
126
175
  - Imports may queue auto-run dispatch evaluation, but only columns whose `autoRun` policy matches the event are queued.
176
+ - Formatter columns are normalized server-side to `onSourceUpdate` (attempted `never`/`onInsert` values are overridden).
127
177
 
128
178
  ## Create a column
129
179
 
@@ -148,6 +198,7 @@ Notes:
148
198
  - `add_to_crm` is optional and non-billable.
149
199
  - Email/phone enrichment does not require creating/running `add_to_crm`.
150
200
  - For `add_to_crm`, required mapping keys are `linkedinUrl` and `companyDomain`.
201
+ - Formatter formulas must use row references (`row['first_name']`, `row.last_name`), not bare template placeholders like ``${first_name}``.
151
202
  - `sync_to_table` supports both:
152
203
  - single destination: `destinationTableId` + `columnMappings`
153
204
  - router mode: `routes[]` (first matching route wins)
@@ -155,6 +206,7 @@ Notes:
155
206
  - `sequenceId`
156
207
  - `sourceLeadColumn` pointing to a lead-id producing column (`add_to_crm` or `lead_finder` output)
157
208
  - auto-attaches research context defaults during enrollment (`source_table_id`, plus optional table name); favorite fields resolve from current starred columns when explicit `fieldIds` are not provided
209
+ - star/favorite high-signal fields so call-sidecar context stays quick to scan and AI draft context stays focused
158
210
 
159
211
  If you create custom `llm_enrichment` schemas:
160
212
  - Use strict field-map schema shape (no root `type/properties` wrapper).
@@ -200,6 +252,65 @@ This keeps quickstart short while documenting the full strategy:
200
252
  - one-person-per-row output contract,
201
253
  - JSON split + downstream enrichment chaining.
202
254
 
255
+ ## First-principles shape: intent + context
256
+
257
+ For most workflows, useful output combines:
258
+ - context: source evidence (what happened / what was observed)
259
+ - intent: interpretation signal (what to prioritize / do next)
260
+
261
+ Design guidance:
262
+ - preserve full-fidelity context in at least one raw field (for example full post/body text)
263
+ - default behavior for agents: write full context/content, not summaries or truncation
264
+ - only truncate/summarize when there is a hard limit (storage/model/provider/payload), and explicitly mark truncation
265
+ - keep intent separate from raw context so downstream logic can change without losing source data
266
+ - for calling workflows, favorite/star key fields so users can access the most relevant context quickly in sidecar views
267
+ - for AI-generated emails/copy, include both intent and full context in imports so drafts are grounded, not generic
268
+ - keep one entity per row and dedupe on a stable identity key
269
+ - keep snippets/summaries optional and derived from raw context, never a replacement for it
270
+ - normalize intent labels if you need deterministic automation
271
+
272
+ Optional field pattern (adapt to your workflow):
273
+ - `post_content` or `context_raw`: full long-form context
274
+ - `context_url`: source URL
275
+ - `context_timestamp`: recency marker
276
+ - `intent_label`: normalized intent category
277
+ - `intent_reason`: concise rationale
278
+
279
+ CSV import note for context fields:
280
+ - long text fields like `post_content` can contain newlines and commas
281
+ - that is valid CSV when fields are properly quoted
282
+ - always run `rows import-csv --validate-only` (or `--dry-run`) before write
283
+ - for strict guardrails, add assertions: `--expected-rows`, `--require-columns`, `--duplicate-key`, `--require-non-empty`
284
+ - import does not intentionally truncate text values; very large single-cell payloads are still bounded by Mongo document limits
285
+
286
+ Verification + rollback commands:
287
+ - `autotouch rows import-verify --table-id <TABLE_ID> --task-id <TASK_ID> --expected-rows <N>`
288
+ - `autotouch rows import-rollback --table-id <TABLE_ID> --task-id <TASK_ID>`
289
+
290
+ Multiline `post_content` examples:
291
+
292
+ ```csv
293
+ # bad (unquoted multiline content breaks row shape)
294
+ first_name,linkedin_url,post_content
295
+ Ada,https://linkedin.com/in/ada,Line 1
296
+ Line 2
297
+ ```
298
+
299
+ ```csv
300
+ # good (quoted multiline content is valid CSV)
301
+ first_name,linkedin_url,post_content
302
+ Ada,https://linkedin.com/in/ada,"Line 1
303
+ Line 2"
304
+ ```
305
+
306
+ Do not re-import blind (recovery flow):
307
+ - stop and keep the original `task_id`
308
+ - inspect status: `autotouch rows import-status --table-id <TABLE_ID> --task-id <TASK_ID>`
309
+ - prove postflight: `autotouch rows import-verify --table-id <TABLE_ID> --task-id <TASK_ID> ...assertions...`
310
+ - if verification fails, preview rollback: `autotouch rows import-rollback --table-id <TABLE_ID> --task-id <TASK_ID> --dry-run`
311
+ - then rollback: `autotouch rows import-rollback --table-id <TABLE_ID> --task-id <TASK_ID>`
312
+ - fix CSV quoting/mapping and run `--validate-only` before any new import
313
+
203
314
  ## Sequences/Tasks workflow APIs (current CLI coverage)
204
315
 
205
316
  The installable `autotouch` CLI currently provides first-class commands for research-table APIs.
@@ -443,6 +554,15 @@ autotouch columns run-next \
443
554
  --wait
444
555
  ```
445
556
 
557
+ Blacklist gate (recommended before billable runs):
558
+
559
+ ```bash
560
+ autotouch blacklist filter \
561
+ --emails-file candidates.csv \
562
+ --emails-column work_email \
563
+ --output json
564
+ ```
565
+
446
566
  ## Job truth contract (agent-safe)
447
567
 
448
568
  - Treat a run as started only when you receive `job_id`.
@@ -498,12 +618,16 @@ If either failure status appears, treat the run as unconfirmed/inconsistent, ver
498
618
  - Re-run with same `--checkpoint-file` and `--wait`.
499
619
  - CLI can resume/poll an in-flight task from checkpoint state.
500
620
 
501
- 4. Run queued but job id missing from local output:
621
+ 4. Need proof before saying "fixed":
622
+ - Run `autotouch rows import-verify --table-id <TABLE_ID> --task-id <TASK_ID> ...assertions...`
623
+ - Treat `passed=false` as a failed import outcome.
624
+
625
+ 5. Run queued but job id missing from local output:
502
626
  - Recover from backend history:
503
627
  - `autotouch jobs list --table-id <TABLE_ID> --column-id <COLUMN_ID> --limit 5 --output json`
504
628
  - Then poll the returned `job_id` with `autotouch jobs get` / `autotouch jobs watch`.
505
629
 
506
- 5. Need to stop a running column job:
630
+ 6. Need to stop a running column job:
507
631
 
508
632
  ```bash
509
633
  autotouch columns stop --table-id <TABLE_ID> --column-id <COLUMN_ID>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "autotouch-cli"
7
- version = "0.2.20"
7
+ version = "0.2.22"
8
8
  description = "Autotouch Smart Table CLI"
9
9
  readme = "docs/research-table/reference/autotouch-cli-pypi.md"
10
10
  requires-python = ">=3.9"