autotouch-cli 0.2.55__tar.gz → 0.2.58__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.
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/PKG-INFO +8 -5
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/README.md +7 -4
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/cli.py +21 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/auth.py +98 -1
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/jobs.py +6 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/rows.py +128 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/search.py +13 -4
- autotouch_cli-0.2.58/autotouch_cli/commands/tables.py +281 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/webhooks.py +35 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/core/run.py +79 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/data/CLI_REFERENCE.md +316 -30
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/data/cli-manifest.json +1993 -145
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/exceptions.py +2 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/parser.py +13 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/parser_groups.py +29 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/templates.py +716 -17
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli.egg-info/PKG-INFO +8 -5
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_shared/provider_registry.py +86 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_shared/search_contract.py +9 -1
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/pyproject.toml +1 -1
- autotouch_cli-0.2.55/autotouch_cli/commands/tables.py +0 -105
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/MANIFEST.in +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/__init__.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/cli_contracts.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/__init__.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/cells.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/columns.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/leads.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/linkedin.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/prompts.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/sequences.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/tasks.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/commands/workspace_secrets.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/core/__init__.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/core/auth.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/core/config.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/core/csv_import.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/core/http.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/core/io.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/core/output.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/core/polling.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/core/validation.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/mongo_status.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli/sequence_support.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli.egg-info/SOURCES.txt +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli.egg-info/dependency_links.txt +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli.egg-info/entry_points.txt +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli.egg-info/requires.txt +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_cli.egg-info/top_level.txt +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_shared/__init__.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/autotouch_shared/linkedin_contract.py +0 -0
- {autotouch_cli-0.2.55 → autotouch_cli-0.2.58}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: autotouch-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.58
|
|
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/
|
|
167
|
-
- Agent playbook: https://github.com/nicolonic/autotouch_main/blob/
|
|
168
|
-
- Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/
|
|
169
|
-
- Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/
|
|
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/
|
|
142
|
-
- Agent playbook: https://github.com/nicolonic/autotouch_main/blob/
|
|
143
|
-
- Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/
|
|
144
|
-
- Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/
|
|
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,
|
|
@@ -160,6 +166,7 @@ from autotouch_cli.commands.tasks import (
|
|
|
160
166
|
)
|
|
161
167
|
from autotouch_cli.commands.webhooks import (
|
|
162
168
|
WebhookCommandRuntime as WebhookCommandHandlerRuntime,
|
|
169
|
+
cmd_webhooks_configure_ingest as cmd_webhooks_configure_ingest_impl,
|
|
163
170
|
cmd_webhooks_deliveries_attempts as cmd_webhooks_deliveries_attempts_impl,
|
|
164
171
|
cmd_webhooks_deliveries_list as cmd_webhooks_deliveries_list_impl,
|
|
165
172
|
cmd_webhooks_get as cmd_webhooks_get_impl,
|
|
@@ -316,6 +323,7 @@ from autotouch_cli.templates import (
|
|
|
316
323
|
emit_webhooks_recipe,
|
|
317
324
|
emit_workflows_list,
|
|
318
325
|
emit_workflows_recipe,
|
|
326
|
+
emit_workflows_scaffold,
|
|
319
327
|
)
|
|
320
328
|
from autotouch_shared.provider_registry import (
|
|
321
329
|
ProviderContractValidationError,
|
|
@@ -1021,6 +1029,9 @@ def _job_snapshot(job_doc: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
1021
1029
|
"processed_rows": int(job_doc.get("processed_rows") or 0),
|
|
1022
1030
|
"total_rows": int(job_doc.get("total_rows") or 0),
|
|
1023
1031
|
"error_rows": int(job_doc.get("error_rows") or 0),
|
|
1032
|
+
"skipped_rows": int(job_doc.get("skipped_rows") or 0),
|
|
1033
|
+
"pending_batches": int(job_doc.get("pending_batches") or 0),
|
|
1034
|
+
"terminal_reason": job_doc.get("terminal_reason"),
|
|
1024
1035
|
"credits_used": float(job_doc.get("credits_used") or 0.0),
|
|
1025
1036
|
"billable": bool(job_doc.get("billable", False)),
|
|
1026
1037
|
"updated_at": job_doc.get("updated_at"),
|
|
@@ -1347,6 +1358,7 @@ def _bound(name: str) -> Callable:
|
|
|
1347
1358
|
|
|
1348
1359
|
# -- Auth commands --
|
|
1349
1360
|
_register("auth_check", cmd_auth_check_impl, _auth_command_runtime)
|
|
1361
|
+
_register("auth_status", cmd_auth_status_impl, _auth_command_runtime)
|
|
1350
1362
|
_register("auth_set_key", cmd_auth_set_key_impl, _auth_command_runtime)
|
|
1351
1363
|
_register("auth_bootstrap", cmd_auth_bootstrap_impl, _auth_command_runtime)
|
|
1352
1364
|
_register("auth_login", cmd_auth_login_impl, _auth_command_runtime)
|
|
@@ -1369,6 +1381,10 @@ _register("workspace_secrets_delete", cmd_workspace_secrets_delete_impl, _worksp
|
|
|
1369
1381
|
# -- Table commands --
|
|
1370
1382
|
_register("tables_list", cmd_tables_list_impl, _table_command_runtime)
|
|
1371
1383
|
_register("tables_create", cmd_tables_create_impl, _table_command_runtime)
|
|
1384
|
+
_register("tables_update", cmd_tables_update_impl, _table_command_runtime)
|
|
1385
|
+
_register("tables_identity_get", cmd_tables_identity_get_impl, _table_command_runtime)
|
|
1386
|
+
_register("tables_identity_set", cmd_tables_identity_set_impl, _table_command_runtime)
|
|
1387
|
+
_register("tables_identity_clear", cmd_tables_identity_clear_impl, _table_command_runtime)
|
|
1372
1388
|
|
|
1373
1389
|
# -- Row commands --
|
|
1374
1390
|
_register("rows_list", cmd_rows_list_impl, _row_command_runtime)
|
|
@@ -1379,6 +1395,7 @@ _register("rows_import_csv", cmd_rows_import_csv_impl, _row_command_runtime)
|
|
|
1379
1395
|
_register("rows_import_status", cmd_rows_import_status_impl, _row_command_runtime)
|
|
1380
1396
|
_register("rows_import_verify", cmd_rows_import_verify_impl, _row_command_runtime)
|
|
1381
1397
|
_register("rows_import_rollback", cmd_rows_import_rollback_impl, _row_command_runtime)
|
|
1398
|
+
_register("rows_dedupe", cmd_rows_dedupe_impl, _row_command_runtime)
|
|
1382
1399
|
|
|
1383
1400
|
# -- Cell commands --
|
|
1384
1401
|
_register("cells_get", cmd_cells_get_impl, _cell_command_runtime)
|
|
@@ -1450,6 +1467,7 @@ _register("webhooks_get", cmd_webhooks_get_impl, _webhook_command_runtime)
|
|
|
1450
1467
|
_register("webhooks_rotate", cmd_webhooks_rotate_impl, _webhook_command_runtime)
|
|
1451
1468
|
_register("webhooks_ingest", cmd_webhooks_ingest_impl, _webhook_command_runtime)
|
|
1452
1469
|
_register("webhooks_recipe", cmd_webhooks_recipe_impl, _webhook_command_runtime)
|
|
1470
|
+
_register("webhooks_configure_ingest", cmd_webhooks_configure_ingest_impl, _webhook_command_runtime)
|
|
1453
1471
|
_register("webhooks_subscriptions_list", cmd_webhooks_subscriptions_list_impl, _webhook_command_runtime)
|
|
1454
1472
|
_register("webhooks_subscriptions_create", cmd_webhooks_subscriptions_create_impl, _webhook_command_runtime)
|
|
1455
1473
|
_register("webhooks_subscriptions_get", cmd_webhooks_subscriptions_get_impl, _webhook_command_runtime)
|
|
@@ -1496,6 +1514,7 @@ _register("sequences_recipe", emit_sequences_recipe, _template_runtime)
|
|
|
1496
1514
|
_register("leads_recipe", emit_leads_recipe, _template_runtime)
|
|
1497
1515
|
_register("workflows_list", emit_workflows_list, _template_runtime)
|
|
1498
1516
|
_register("workflows_recipe", emit_workflows_recipe, _template_runtime)
|
|
1517
|
+
_register("workflows_scaffold", emit_workflows_scaffold, _template_runtime)
|
|
1499
1518
|
|
|
1500
1519
|
# -- Special-case commands that pass extra kwargs --
|
|
1501
1520
|
|
|
@@ -2265,6 +2284,7 @@ def _load_bundled_cli_reference() -> Optional[str]:
|
|
|
2265
2284
|
# ---------------------------------------------------------------------------
|
|
2266
2285
|
cmd_auth_bootstrap = _bound("auth_bootstrap")
|
|
2267
2286
|
cmd_auth_check = _bound("auth_check")
|
|
2287
|
+
cmd_auth_status = _bound("auth_status")
|
|
2268
2288
|
cmd_auth_schema = _bound("auth_schema")
|
|
2269
2289
|
cmd_auth_whoami = _bound("auth_whoami")
|
|
2270
2290
|
cmd_cells_get = _bound("cells_get")
|
|
@@ -2280,6 +2300,7 @@ cmd_schema = _bound("schema")
|
|
|
2280
2300
|
cmd_sequences_enroll = _bound("sequences_enroll")
|
|
2281
2301
|
cmd_sequences_status = _bound("sequences_status")
|
|
2282
2302
|
cmd_tasks_recipe = _bound("tasks_recipe")
|
|
2303
|
+
cmd_workflows_scaffold = _bound("workflows_scaffold")
|
|
2283
2304
|
cmd_webhooks_recipe = _bound("webhooks_recipe")
|
|
2284
2305
|
cmd_workspace_secrets_delete = _bound("workspace_secrets_delete")
|
|
2285
2306
|
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(
|
|
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")
|