autotouch-cli 0.2.55__tar.gz → 0.2.59__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 (52) hide show
  1. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/PKG-INFO +8 -5
  2. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/README.md +7 -4
  3. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/cli.py +31 -0
  4. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/auth.py +98 -1
  5. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/jobs.py +6 -0
  6. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/rows.py +128 -0
  7. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/search.py +13 -4
  8. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/sequences.py +123 -17
  9. autotouch_cli-0.2.59/autotouch_cli/commands/tables.py +281 -0
  10. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/tasks.py +98 -0
  11. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/webhooks.py +35 -0
  12. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/core/http.py +8 -2
  13. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/core/run.py +79 -0
  14. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/data/CLI_REFERENCE.md +506 -38
  15. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/data/cli-manifest.json +6573 -3616
  16. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/exceptions.py +2 -0
  17. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/parser.py +18 -0
  18. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/parser_groups.py +85 -0
  19. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/sequence_support.py +36 -2
  20. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/templates.py +755 -17
  21. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli.egg-info/PKG-INFO +8 -5
  22. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_shared/provider_registry.py +106 -1
  23. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_shared/search_contract.py +9 -1
  24. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/pyproject.toml +1 -1
  25. autotouch_cli-0.2.55/autotouch_cli/commands/tables.py +0 -105
  26. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/MANIFEST.in +0 -0
  27. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/__init__.py +0 -0
  28. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/cli_contracts.py +0 -0
  29. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/__init__.py +0 -0
  30. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/cells.py +0 -0
  31. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/columns.py +0 -0
  32. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/leads.py +0 -0
  33. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/linkedin.py +0 -0
  34. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/prompts.py +0 -0
  35. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/commands/workspace_secrets.py +0 -0
  36. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/core/__init__.py +0 -0
  37. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/core/auth.py +0 -0
  38. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/core/config.py +0 -0
  39. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/core/csv_import.py +0 -0
  40. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/core/io.py +0 -0
  41. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/core/output.py +0 -0
  42. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/core/polling.py +0 -0
  43. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/core/validation.py +0 -0
  44. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli/mongo_status.py +0 -0
  45. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli.egg-info/SOURCES.txt +0 -0
  46. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli.egg-info/dependency_links.txt +0 -0
  47. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli.egg-info/entry_points.txt +0 -0
  48. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli.egg-info/requires.txt +0 -0
  49. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_cli.egg-info/top_level.txt +0 -0
  50. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_shared/__init__.py +0 -0
  51. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/autotouch_shared/linkedin_contract.py +0 -0
  52. {autotouch_cli-0.2.55 → autotouch_cli-0.2.59}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autotouch-cli
3
- Version: 0.2.55
3
+ Version: 0.2.59
4
4
  Summary: Autotouch Smart Table CLI
5
5
  Project-URL: Homepage, https://app.autotouch.ai
6
6
  Project-URL: Documentation, https://github.com/nicolonic/autotouch_main/tree/main/docs/research-table/reference
@@ -40,6 +40,8 @@ For a shipped human-readable reference generated from the installed parser, use
40
40
  pipx install autotouch-cli
41
41
  # or
42
42
  pip install autotouch-cli
43
+ # upgrade
44
+ pip install -U autotouch-cli
43
45
  ```
44
46
 
45
47
  ## First Run
@@ -135,6 +137,7 @@ autotouch rows get --table-id "$TABLE_ID" --row-id "$ROW_ID" --output json
135
137
  - Poll authoritative state: `autotouch jobs get`
136
138
  - Create/activate sequences: `autotouch sequences recipe`, `autotouch sequences create`, `autotouch sequences activate`
137
139
  - Query leads: `autotouch leads query`
140
+ - Find a lead by email or phone: `autotouch leads query --search '<email-or-phone>' --limit 10`
138
141
 
139
142
  ## More
140
143
 
@@ -163,7 +166,7 @@ Prompt variables in authored prompts support nested JSON access:
163
166
 
164
167
  ## Docs
165
168
 
166
- - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.54/docs/research-table/reference/autotouch-cli.md
167
- - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.54/docs/research-table/guides/autotouch-cli-agent-playbook.md
168
- - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.54/docs/research-table/reference/tables-api.md
169
- - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.54/docs/platform/authentication.md
169
+ - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/main/docs/research-table/reference/autotouch-cli.md
170
+ - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/main/docs/research-table/guides/autotouch-cli-agent-playbook.md
171
+ - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/main/docs/research-table/reference/tables-api.md
172
+ - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/main/docs/platform/authentication.md
@@ -15,6 +15,8 @@ For a shipped human-readable reference generated from the installed parser, use
15
15
  pipx install autotouch-cli
16
16
  # or
17
17
  pip install autotouch-cli
18
+ # upgrade
19
+ pip install -U autotouch-cli
18
20
  ```
19
21
 
20
22
  ## First Run
@@ -110,6 +112,7 @@ autotouch rows get --table-id "$TABLE_ID" --row-id "$ROW_ID" --output json
110
112
  - Poll authoritative state: `autotouch jobs get`
111
113
  - Create/activate sequences: `autotouch sequences recipe`, `autotouch sequences create`, `autotouch sequences activate`
112
114
  - Query leads: `autotouch leads query`
115
+ - Find a lead by email or phone: `autotouch leads query --search '<email-or-phone>' --limit 10`
113
116
 
114
117
  ## More
115
118
 
@@ -138,7 +141,7 @@ Prompt variables in authored prompts support nested JSON access:
138
141
 
139
142
  ## Docs
140
143
 
141
- - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.54/docs/research-table/reference/autotouch-cli.md
142
- - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.54/docs/research-table/guides/autotouch-cli-agent-playbook.md
143
- - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.54/docs/research-table/reference/tables-api.md
144
- - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.54/docs/platform/authentication.md
144
+ - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/main/docs/research-table/reference/autotouch-cli.md
145
+ - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/main/docs/research-table/guides/autotouch-cli-agent-playbook.md
146
+ - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/main/docs/research-table/reference/tables-api.md
147
+ - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/main/docs/platform/authentication.md
@@ -64,6 +64,7 @@ from autotouch_cli.commands.auth import (
64
64
  cmd_auth_check as cmd_auth_check_impl,
65
65
  cmd_auth_clear as cmd_auth_clear_impl,
66
66
  cmd_auth_login as cmd_auth_login_impl,
67
+ cmd_auth_status as cmd_auth_status_impl,
67
68
  cmd_auth_set_key as cmd_auth_set_key_impl,
68
69
  cmd_auth_show as cmd_auth_show_impl,
69
70
  cmd_auth_whoami as cmd_auth_whoami_impl,
@@ -104,6 +105,7 @@ from autotouch_cli.commands.linkedin import (
104
105
  from autotouch_cli.commands.rows import (
105
106
  RowCommandRuntime as RowCommandHandlerRuntime,
106
107
  cmd_rows_add as cmd_rows_add_impl,
108
+ cmd_rows_dedupe as cmd_rows_dedupe_impl,
107
109
  cmd_rows_delete as cmd_rows_delete_impl,
108
110
  cmd_rows_get as cmd_rows_get_impl,
109
111
  cmd_rows_import_csv as cmd_rows_import_csv_impl,
@@ -141,6 +143,10 @@ from autotouch_cli.commands.tables import (
141
143
  TableCommandRuntime as TableCommandHandlerRuntime,
142
144
  cmd_tables_create as cmd_tables_create_impl,
143
145
  cmd_tables_list as cmd_tables_list_impl,
146
+ cmd_tables_update as cmd_tables_update_impl,
147
+ cmd_tables_identity_get as cmd_tables_identity_get_impl,
148
+ cmd_tables_identity_set as cmd_tables_identity_set_impl,
149
+ cmd_tables_identity_clear as cmd_tables_identity_clear_impl,
144
150
  )
145
151
  from autotouch_cli.commands.tasks import (
146
152
  TaskCommandRuntime as TaskCommandHandlerRuntime,
@@ -152,14 +158,20 @@ from autotouch_cli.commands.tasks import (
152
158
  cmd_tasks_delete as cmd_tasks_delete_impl,
153
159
  cmd_tasks_draft as cmd_tasks_draft_impl,
154
160
  cmd_tasks_get as cmd_tasks_get_impl,
161
+ cmd_tasks_prioritize_email as cmd_tasks_prioritize_email_impl,
162
+ cmd_tasks_prioritize_linkedin as cmd_tasks_prioritize_linkedin_impl,
155
163
  cmd_tasks_query as cmd_tasks_query_impl,
156
164
  cmd_tasks_recipe as cmd_tasks_recipe_impl,
165
+ cmd_tasks_reschedule_email as cmd_tasks_reschedule_email_impl,
166
+ cmd_tasks_reschedule_linkedin as cmd_tasks_reschedule_linkedin_impl,
157
167
  cmd_tasks_schedule_email as cmd_tasks_schedule_email_impl,
168
+ cmd_tasks_schedule_linkedin as cmd_tasks_schedule_linkedin_impl,
158
169
  cmd_tasks_stats as cmd_tasks_stats_impl,
159
170
  cmd_tasks_update as cmd_tasks_update_impl,
160
171
  )
161
172
  from autotouch_cli.commands.webhooks import (
162
173
  WebhookCommandRuntime as WebhookCommandHandlerRuntime,
174
+ cmd_webhooks_configure_ingest as cmd_webhooks_configure_ingest_impl,
163
175
  cmd_webhooks_deliveries_attempts as cmd_webhooks_deliveries_attempts_impl,
164
176
  cmd_webhooks_deliveries_list as cmd_webhooks_deliveries_list_impl,
165
177
  cmd_webhooks_get as cmd_webhooks_get_impl,
@@ -316,6 +328,7 @@ from autotouch_cli.templates import (
316
328
  emit_webhooks_recipe,
317
329
  emit_workflows_list,
318
330
  emit_workflows_recipe,
331
+ emit_workflows_scaffold,
319
332
  )
320
333
  from autotouch_shared.provider_registry import (
321
334
  ProviderContractValidationError,
@@ -1021,6 +1034,9 @@ def _job_snapshot(job_doc: Dict[str, Any]) -> Dict[str, Any]:
1021
1034
  "processed_rows": int(job_doc.get("processed_rows") or 0),
1022
1035
  "total_rows": int(job_doc.get("total_rows") or 0),
1023
1036
  "error_rows": int(job_doc.get("error_rows") or 0),
1037
+ "skipped_rows": int(job_doc.get("skipped_rows") or 0),
1038
+ "pending_batches": int(job_doc.get("pending_batches") or 0),
1039
+ "terminal_reason": job_doc.get("terminal_reason"),
1024
1040
  "credits_used": float(job_doc.get("credits_used") or 0.0),
1025
1041
  "billable": bool(job_doc.get("billable", False)),
1026
1042
  "updated_at": job_doc.get("updated_at"),
@@ -1347,6 +1363,7 @@ def _bound(name: str) -> Callable:
1347
1363
 
1348
1364
  # -- Auth commands --
1349
1365
  _register("auth_check", cmd_auth_check_impl, _auth_command_runtime)
1366
+ _register("auth_status", cmd_auth_status_impl, _auth_command_runtime)
1350
1367
  _register("auth_set_key", cmd_auth_set_key_impl, _auth_command_runtime)
1351
1368
  _register("auth_bootstrap", cmd_auth_bootstrap_impl, _auth_command_runtime)
1352
1369
  _register("auth_login", cmd_auth_login_impl, _auth_command_runtime)
@@ -1369,6 +1386,10 @@ _register("workspace_secrets_delete", cmd_workspace_secrets_delete_impl, _worksp
1369
1386
  # -- Table commands --
1370
1387
  _register("tables_list", cmd_tables_list_impl, _table_command_runtime)
1371
1388
  _register("tables_create", cmd_tables_create_impl, _table_command_runtime)
1389
+ _register("tables_update", cmd_tables_update_impl, _table_command_runtime)
1390
+ _register("tables_identity_get", cmd_tables_identity_get_impl, _table_command_runtime)
1391
+ _register("tables_identity_set", cmd_tables_identity_set_impl, _table_command_runtime)
1392
+ _register("tables_identity_clear", cmd_tables_identity_clear_impl, _table_command_runtime)
1372
1393
 
1373
1394
  # -- Row commands --
1374
1395
  _register("rows_list", cmd_rows_list_impl, _row_command_runtime)
@@ -1379,6 +1400,7 @@ _register("rows_import_csv", cmd_rows_import_csv_impl, _row_command_runtime)
1379
1400
  _register("rows_import_status", cmd_rows_import_status_impl, _row_command_runtime)
1380
1401
  _register("rows_import_verify", cmd_rows_import_verify_impl, _row_command_runtime)
1381
1402
  _register("rows_import_rollback", cmd_rows_import_rollback_impl, _row_command_runtime)
1403
+ _register("rows_dedupe", cmd_rows_dedupe_impl, _row_command_runtime)
1382
1404
 
1383
1405
  # -- Cell commands --
1384
1406
  _register("cells_get", cmd_cells_get_impl, _cell_command_runtime)
@@ -1443,6 +1465,11 @@ _register("tasks_bulk_update", cmd_tasks_bulk_update_impl, _task_command_runtime
1443
1465
  _register("tasks_bulk_delete", cmd_tasks_bulk_delete_impl, _task_command_runtime)
1444
1466
  _register("tasks_draft", cmd_tasks_draft_impl, _task_command_runtime)
1445
1467
  _register("tasks_schedule_email", cmd_tasks_schedule_email_impl, _task_command_runtime)
1468
+ _register("tasks_prioritize_email", cmd_tasks_prioritize_email_impl, _task_command_runtime)
1469
+ _register("tasks_reschedule_email", cmd_tasks_reschedule_email_impl, _task_command_runtime)
1470
+ _register("tasks_schedule_linkedin", cmd_tasks_schedule_linkedin_impl, _task_command_runtime)
1471
+ _register("tasks_prioritize_linkedin", cmd_tasks_prioritize_linkedin_impl, _task_command_runtime)
1472
+ _register("tasks_reschedule_linkedin", cmd_tasks_reschedule_linkedin_impl, _task_command_runtime)
1446
1473
  _register("tasks_recipe", cmd_tasks_recipe_impl, _task_command_runtime)
1447
1474
 
1448
1475
  # -- Webhook commands --
@@ -1450,6 +1477,7 @@ _register("webhooks_get", cmd_webhooks_get_impl, _webhook_command_runtime)
1450
1477
  _register("webhooks_rotate", cmd_webhooks_rotate_impl, _webhook_command_runtime)
1451
1478
  _register("webhooks_ingest", cmd_webhooks_ingest_impl, _webhook_command_runtime)
1452
1479
  _register("webhooks_recipe", cmd_webhooks_recipe_impl, _webhook_command_runtime)
1480
+ _register("webhooks_configure_ingest", cmd_webhooks_configure_ingest_impl, _webhook_command_runtime)
1453
1481
  _register("webhooks_subscriptions_list", cmd_webhooks_subscriptions_list_impl, _webhook_command_runtime)
1454
1482
  _register("webhooks_subscriptions_create", cmd_webhooks_subscriptions_create_impl, _webhook_command_runtime)
1455
1483
  _register("webhooks_subscriptions_get", cmd_webhooks_subscriptions_get_impl, _webhook_command_runtime)
@@ -1496,6 +1524,7 @@ _register("sequences_recipe", emit_sequences_recipe, _template_runtime)
1496
1524
  _register("leads_recipe", emit_leads_recipe, _template_runtime)
1497
1525
  _register("workflows_list", emit_workflows_list, _template_runtime)
1498
1526
  _register("workflows_recipe", emit_workflows_recipe, _template_runtime)
1527
+ _register("workflows_scaffold", emit_workflows_scaffold, _template_runtime)
1499
1528
 
1500
1529
  # -- Special-case commands that pass extra kwargs --
1501
1530
 
@@ -2265,6 +2294,7 @@ def _load_bundled_cli_reference() -> Optional[str]:
2265
2294
  # ---------------------------------------------------------------------------
2266
2295
  cmd_auth_bootstrap = _bound("auth_bootstrap")
2267
2296
  cmd_auth_check = _bound("auth_check")
2297
+ cmd_auth_status = _bound("auth_status")
2268
2298
  cmd_auth_schema = _bound("auth_schema")
2269
2299
  cmd_auth_whoami = _bound("auth_whoami")
2270
2300
  cmd_cells_get = _bound("cells_get")
@@ -2280,6 +2310,7 @@ cmd_schema = _bound("schema")
2280
2310
  cmd_sequences_enroll = _bound("sequences_enroll")
2281
2311
  cmd_sequences_status = _bound("sequences_status")
2282
2312
  cmd_tasks_recipe = _bound("tasks_recipe")
2313
+ cmd_workflows_scaffold = _bound("workflows_scaffold")
2283
2314
  cmd_webhooks_recipe = _bound("webhooks_recipe")
2284
2315
  cmd_workspace_secrets_delete = _bound("workspace_secrets_delete")
2285
2316
  cmd_workspace_secrets_schema = _bound("workspace_secrets_schema")
@@ -45,6 +45,86 @@ def cmd_auth_check(args: argparse.Namespace, *, runtime: AuthCommandRuntime) ->
45
45
  runtime.print_json(output, args.compact)
46
46
 
47
47
 
48
+ def cmd_auth_status(args: argparse.Namespace, *, runtime: AuthCommandRuntime) -> None:
49
+ token = runtime.resolve_token(args.token, required=True)
50
+ check_body = runtime.request_api(
51
+ "GET",
52
+ "/api/auth/check",
53
+ base_url=args.base_url,
54
+ token=token,
55
+ use_x_api_key=args.use_x_api_key,
56
+ timeout=args.timeout,
57
+ verbose=args.verbose,
58
+ )
59
+
60
+ details_body: Any = None
61
+ details_warning: Optional[str] = None
62
+ try:
63
+ details_body = runtime.request_api(
64
+ "GET",
65
+ "/api/auth/user-details",
66
+ base_url=args.base_url,
67
+ token=token,
68
+ use_x_api_key=args.use_x_api_key,
69
+ timeout=args.timeout,
70
+ verbose=args.verbose,
71
+ )
72
+ except SystemExit as exc:
73
+ details_warning = f"user-details lookup failed with exit code {exc.code}"
74
+ except Exception as exc:
75
+ details_warning = str(exc)
76
+
77
+ output: Dict[str, Any] = {
78
+ "ok": bool((check_body or {}).get("ok", True)) if isinstance(check_body, dict) else True,
79
+ "base_url": runtime.api_url(args.base_url),
80
+ "auth_header_mode": "x-api-key" if args.use_x_api_key else "authorization",
81
+ }
82
+
83
+ if isinstance(check_body, dict):
84
+ for key in (
85
+ "auth_type",
86
+ "user_id",
87
+ "email",
88
+ "organization_id",
89
+ "api_key_id",
90
+ "api_key_name",
91
+ "granted_scopes",
92
+ ):
93
+ if key in check_body:
94
+ output[key] = check_body.get(key)
95
+
96
+ if isinstance(details_body, dict):
97
+ for key in (
98
+ "first_name",
99
+ "last_name",
100
+ "title",
101
+ "organization_name",
102
+ "organization_domain",
103
+ "organization_credits",
104
+ "organization_credits_monthly_quota",
105
+ "organization_next_renewal_date",
106
+ "user_allowance",
107
+ "user_monthly_quota",
108
+ "credit_access",
109
+ "permissions",
110
+ "plan",
111
+ "confirmed",
112
+ ):
113
+ if key in details_body:
114
+ output[key] = details_body.get(key)
115
+ if output.get("user_id") is None and details_body.get("id") is not None:
116
+ output["user_id"] = details_body.get("id")
117
+ if output.get("email") is None and details_body.get("email") is not None:
118
+ output["email"] = details_body.get("email")
119
+ if output.get("organization_id") is None and details_body.get("organization_id") is not None:
120
+ output["organization_id"] = details_body.get("organization_id")
121
+
122
+ if details_warning:
123
+ output["details_warning"] = details_warning
124
+
125
+ runtime.print_json(output, args.compact)
126
+
127
+
48
128
  def cmd_auth_set_key(args: argparse.Namespace, *, runtime: AuthCommandRuntime) -> None:
49
129
  api_key = (args.api_key or "").strip() if getattr(args, "api_key", None) else None
50
130
  if not api_key:
@@ -357,6 +437,18 @@ def register_auth_subcommands(
357
437
  add_api_common_arguments(pa)
358
438
  pa.set_defaults(func=handlers["check"])
359
439
 
440
+ pastatus = auth_sub.add_parser(
441
+ "status",
442
+ help="Resolve auth, identity, and credit/allowance context",
443
+ description=(
444
+ "Fetch the effective auth context plus organization/user credit state. "
445
+ "This is the best one-command status check when a key is valid but pointed at the wrong org "
446
+ "or the real blocker is allowance/quota state rather than authentication."
447
+ ),
448
+ )
449
+ add_api_common_arguments(pastatus)
450
+ pastatus.set_defaults(func=handlers["status"])
451
+
360
452
  pab = auth_sub.add_parser("bootstrap", help="Register an account/org and return a developer key")
361
453
  pab.add_argument("--first-name", help="First name")
362
454
  pab.add_argument("--last-name", help="Last name")
@@ -396,7 +488,12 @@ def register_auth_subcommands(
396
488
  add_output_formatting_arguments(pask)
397
489
  pask.set_defaults(func=handlers["set_key"])
398
490
 
399
- pashow = auth_sub.add_parser("show", help="Show current auth config")
491
+ pashow = auth_sub.add_parser(
492
+ "show",
493
+ aliases=["config"],
494
+ help="Show current local auth config",
495
+ description="Show the locally saved auth config only. This command does not contact the API.",
496
+ )
400
497
  add_output_formatting_arguments(pashow)
401
498
  pashow.set_defaults(func=handlers["show"])
402
499
 
@@ -82,6 +82,12 @@ def cmd_jobs_watch(args: argparse.Namespace, *, runtime: JobCommandRuntime) -> N
82
82
  "event": "job.timed_out" if timed_out else "job.completed",
83
83
  "job_id": final_job.get("job_id") or final_job.get("jobId") or args.job_id,
84
84
  "status": final_job.get("status"),
85
+ "processed_rows": int(final_job.get("processed_rows") or 0),
86
+ "error_rows": int(final_job.get("error_rows") or 0),
87
+ "skipped_rows": int(final_job.get("skipped_rows") or 0),
88
+ "total_rows": int(final_job.get("total_rows") or 0),
89
+ "pending_batches": int(final_job.get("pending_batches") or 0),
90
+ "terminal_reason": final_job.get("terminal_reason"),
85
91
  "job_status_url": f"{runtime.api_url(args.base_url)}/api/bulk-jobs/{args.job_id}",
86
92
  "timed_out": timed_out,
87
93
  "polls": int(poll_result.get("polls") or 0),
@@ -35,6 +35,23 @@ class RowCommandRuntime:
35
35
  default_timeout_seconds: int
36
36
 
37
37
 
38
+ def _parse_json_cell_values(value: Any) -> Any:
39
+ if isinstance(value, str):
40
+ stripped = value.strip()
41
+ if stripped[:1] in {"{", "["}:
42
+ try:
43
+ parsed = json.loads(stripped)
44
+ except Exception:
45
+ return value
46
+ return _parse_json_cell_values(parsed)
47
+ return value
48
+ if isinstance(value, list):
49
+ return [_parse_json_cell_values(item) for item in value]
50
+ if isinstance(value, dict):
51
+ return {key: _parse_json_cell_values(item) for key, item in value.items()}
52
+ return value
53
+
54
+
38
55
  def cmd_rows_list(args: argparse.Namespace, *, runtime: RowCommandRuntime) -> None:
39
56
  token = runtime.resolve_token(args.token, required=True)
40
57
  filters = runtime.load_json_input(
@@ -73,6 +90,8 @@ def cmd_rows_list(args: argparse.Namespace, *, runtime: RowCommandRuntime) -> No
73
90
  timeout=args.timeout,
74
91
  verbose=args.verbose,
75
92
  )
93
+ if bool(getattr(args, "parse_json_cells", False)):
94
+ data = _parse_json_cell_values(data)
76
95
  runtime.print_json(data, args.compact)
77
96
 
78
97
 
@@ -87,6 +106,8 @@ def cmd_rows_get(args: argparse.Namespace, *, runtime: RowCommandRuntime) -> Non
87
106
  timeout=args.timeout,
88
107
  verbose=args.verbose,
89
108
  )
109
+ if bool(getattr(args, "parse_json_cells", False)):
110
+ data = _parse_json_cell_values(data)
90
111
  runtime.print_json(data, args.compact)
91
112
 
92
113
 
@@ -489,6 +510,23 @@ def cmd_rows_import_csv(args: argparse.Namespace, *, runtime: RowCommandRuntime)
489
510
  if field_mappings is not None:
490
511
  form_fields["field_mappings"] = json.dumps(field_mappings)
491
512
 
513
+ ingest_mode = str(getattr(args, "ingest_mode", None) or "").strip()
514
+ if ingest_mode:
515
+ ingest_policy: Dict[str, Any] = {"mode": ingest_mode}
516
+ match_rule_id = str(getattr(args, "match_rule_id", None) or "").strip()
517
+ match_column = str(getattr(args, "match_column", None) or "").strip()
518
+ match_strategy = str(getattr(args, "match_strategy", None) or "").strip()
519
+ merge_mode = str(getattr(args, "merge_mode", None) or "").strip()
520
+ if match_rule_id:
521
+ ingest_policy["rule_id"] = match_rule_id
522
+ if match_column:
523
+ ingest_policy["column_key"] = match_column
524
+ if match_strategy:
525
+ ingest_policy["match_strategy"] = match_strategy
526
+ if merge_mode:
527
+ ingest_policy["merge_mode"] = merge_mode
528
+ form_fields["ingest_policy"] = json.dumps(ingest_policy)
529
+
492
530
  use_async = bool(getattr(args, "use_async", True))
493
531
  params = {"use_async": "true" if use_async else "false"}
494
532
  response = runtime.request_multipart_api(
@@ -718,6 +756,50 @@ def cmd_rows_import_rollback(args: argparse.Namespace, *, runtime: RowCommandRun
718
756
  raise AutotouchAPIError(f"import rollback had {failed_count} failures")
719
757
 
720
758
 
759
+ def cmd_rows_dedupe(args: argparse.Namespace, *, runtime: RowCommandRuntime) -> None:
760
+ token = runtime.resolve_token(args.token, required=True)
761
+ stats_only = bool(getattr(args, "stats_only", False))
762
+ output_format = str(getattr(args, "output", "json") or "json").strip().lower()
763
+
764
+ if stats_only:
765
+ data = runtime.request_api(
766
+ "GET",
767
+ f"/api/tables/{args.table_id}/dedupe/{args.column_key}/stats",
768
+ base_url=args.base_url,
769
+ token=token,
770
+ use_x_api_key=args.use_x_api_key,
771
+ timeout=args.timeout,
772
+ verbose=args.verbose,
773
+ )
774
+ if output_format == "json" or not isinstance(data, dict):
775
+ runtime.print_json(data, args.compact)
776
+ return
777
+ total_rows = data.get("total_rows", "?")
778
+ unique_values = data.get("unique_values", "?")
779
+ rows_to_delete = data.get("rows_to_delete", "?")
780
+ print(f"Column: {args.column_key}")
781
+ print(f"Total rows: {total_rows}")
782
+ print(f"Unique values: {unique_values}")
783
+ print(f"Rows to delete: {rows_to_delete}")
784
+ print(f"(Run again without --stats-only / --dry-run to delete duplicates)")
785
+ else:
786
+ data = runtime.request_api(
787
+ "POST",
788
+ f"/api/tables/{args.table_id}/dedupe",
789
+ base_url=args.base_url,
790
+ token=token,
791
+ use_x_api_key=args.use_x_api_key,
792
+ payload={"column_key": args.column_key},
793
+ timeout=args.timeout,
794
+ verbose=args.verbose,
795
+ )
796
+ if output_format == "json" or not isinstance(data, dict):
797
+ runtime.print_json(data, args.compact)
798
+ return
799
+ rows_deleted = data.get("rows_deleted", data.get("deleted", "?"))
800
+ print(f"Dedupe complete: {rows_deleted} duplicate rows deleted from column '{args.column_key}'")
801
+
802
+
721
803
  def register_rows_subcommands(
722
804
  subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
723
805
  *,
@@ -737,12 +819,22 @@ def register_rows_subcommands(
737
819
  prl.add_argument("--sort-file", help="Optional JSON file for server-side sort")
738
820
  prl.add_argument("--page-size", type=int, default=50, help="Rows per page (1-1000, default 50)")
739
821
  prl.add_argument("--cursor", help="Pagination cursor from a previous rows list response")
822
+ prl.add_argument(
823
+ "--parse-json-cells",
824
+ action="store_true",
825
+ help="Parse stringified JSON object/array cell values before printing output",
826
+ )
740
827
  add_api_common_arguments(prl)
741
828
  prl.set_defaults(func=handlers["list"])
742
829
 
743
830
  prg = rows_sub.add_parser("get", help="Get one flattened row by id")
744
831
  prg.add_argument("--table-id", required=True)
745
832
  prg.add_argument("--row-id", required=True)
833
+ prg.add_argument(
834
+ "--parse-json-cells",
835
+ action="store_true",
836
+ help="Parse stringified JSON object/array cell values before printing output",
837
+ )
746
838
  add_api_common_arguments(prg)
747
839
  prg.set_defaults(func=handlers["get"])
748
840
 
@@ -830,11 +922,47 @@ def register_rows_subcommands(
830
922
  pric.add_argument("--wait", action="store_true", help="Wait for async optimized import to complete")
831
923
  pric.add_argument("--poll-interval", type=int, default=2, help="Polling interval seconds for --wait")
832
924
  pric.add_argument("--wait-timeout", type=int, default=0, help="Max seconds to wait (0 = no timeout)")
925
+ _INGEST_MODES = ["allow", "skip", "upsert"]
926
+ _MATCH_STRATEGIES = ["email", "domain", "linkedin_url", "external_id", "exact_text"]
927
+ _MERGE_MODES = ["fill_empty", "overwrite_mapped"]
928
+ pric.add_argument(
929
+ "--ingest-mode",
930
+ choices=_INGEST_MODES,
931
+ default=None,
932
+ help="Duplicate handling: allow / skip / upsert (enables server-side identity resolution)",
933
+ )
934
+ pric.add_argument("--match-rule-id", default=None, help="Use a pre-configured table identity rule by ID")
935
+ pric.add_argument("--match-column", default=None, help="Column key to match on (when no rule-id)")
936
+ pric.add_argument(
937
+ "--match-strategy",
938
+ choices=_MATCH_STRATEGIES,
939
+ default=None,
940
+ help="Normalisation strategy for the match column",
941
+ )
942
+ pric.add_argument(
943
+ "--merge-mode",
944
+ choices=_MERGE_MODES,
945
+ default=None,
946
+ help="Cell merge strategy when upserting (default: fill_empty)",
947
+ )
833
948
  pric.set_defaults(trim_headers=True, empty_as_null=True, skip_empty_rows=True)
834
949
  pric.set_defaults(check_company_blacklist=True, check_email_blacklist=False, use_async=True)
835
950
  add_api_common_arguments(pric)
836
951
  pric.set_defaults(func=handlers["import_csv"])
837
952
 
953
+ prdedupe = rows_sub.add_parser("dedupe", help="Preview or remove duplicate rows by column value")
954
+ prdedupe.add_argument("--table-id", required=True)
955
+ prdedupe.add_argument("--column-key", required=True, help="Column to deduplicate on")
956
+ prdedupe.add_argument(
957
+ "--stats-only",
958
+ "--dry-run",
959
+ dest="stats_only",
960
+ action="store_true",
961
+ help="Print deduplication preview without deleting rows",
962
+ )
963
+ add_api_common_arguments(prdedupe)
964
+ prdedupe.set_defaults(func=handlers["dedupe"])
965
+
838
966
  prschema = rows_sub.add_parser("schema", help="Print row payload schemas for add/import helpers")
839
967
  prschema.add_argument("--type", choices=["all", *row_schema_types], default="all")
840
968
  prschema.add_argument("--out-file", help="Write schema JSON to file")
@@ -119,13 +119,20 @@ def cmd_search_people(args: argparse.Namespace, *, runtime: SearchCommandRuntime
119
119
 
120
120
  def cmd_search_similar_companies(args: argparse.Namespace, *, runtime: SearchCommandRuntime) -> None:
121
121
  token = runtime.resolve_token(args.token, required=True)
122
+ payload: Dict[str, Any] = {
123
+ "company_name": str(getattr(args, "company_name", "") or "").strip(),
124
+ "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 100)),
125
+ }
126
+ company_domain = str(getattr(args, "company_domain", "") or "").strip()
127
+ company_description = str(getattr(args, "company_description", "") or "").strip()
128
+ if company_domain:
129
+ payload["company_domain"] = company_domain
130
+ if company_description:
131
+ payload["company_description"] = company_description
122
132
  payload = _load_search_payload(
123
133
  args,
124
134
  runtime=runtime,
125
- default_payload={
126
- "company_name": str(getattr(args, "company_name", "") or "").strip(),
127
- "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 100)),
128
- },
135
+ default_payload=payload,
129
136
  )
130
137
  data = runtime.request_api(
131
138
  "POST",
@@ -278,6 +285,8 @@ def register_search_subcommands(
278
285
 
279
286
  similar_parser = search_sub.add_parser("similar-companies", help="Find companies similar to a seed company")
280
287
  similar_parser.add_argument("--company-name", help="Seed company name")
288
+ similar_parser.add_argument("--company-domain", help="Official company domain to ground similarity search")
289
+ similar_parser.add_argument("--company-description", help="Short semantic company description to improve peer matching")
281
290
  similar_parser.add_argument("--limit", type=int, default=25, help="Max results, 1-100 (default: 25)")
282
291
  similar_parser.add_argument("--data-json", help="Explicit request payload JSON")
283
292
  similar_parser.add_argument("--data-file", help="Path to request payload JSON file")