autotouch-cli 0.2.69__tar.gz → 0.2.72__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.69 → autotouch_cli-0.2.72}/PKG-INFO +11 -1
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/README.md +10 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/cli.py +5 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/cli_contracts.py +1 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/agents.py +160 -3
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/linkedin.py +29 -3
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/core/http.py +14 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/data/CLI_REFERENCE.md +449 -5
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/data/cli-manifest.json +491 -10
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/parser.py +1 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli.egg-info/PKG-INFO +11 -1
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli.egg-info/SOURCES.txt +1 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_shared/linkedin_contract.py +14 -8
- autotouch_cli-0.2.72/autotouch_shared/linkedin_filters.py +660 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_shared/provider_registry.py +3 -1
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/pyproject.toml +1 -1
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/MANIFEST.in +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/__init__.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/__init__.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/auth.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/cells.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/columns.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/jobs.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/leads.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/prompts.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/rows.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/search.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/sequences.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/tables.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/tasks.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/webhooks.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/commands/workspace_secrets.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/core/__init__.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/core/auth.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/core/config.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/core/csv_import.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/core/io.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/core/output.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/core/polling.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/core/run.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/core/validation.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/exceptions.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/mongo_status.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/parser_groups.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/sequence_support.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli/templates.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli.egg-info/dependency_links.txt +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli.egg-info/entry_points.txt +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli.egg-info/requires.txt +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_cli.egg-info/top_level.txt +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_shared/__init__.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/autotouch_shared/search_contract.py +0 -0
- {autotouch_cli-0.2.69 → autotouch_cli-0.2.72}/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.72
|
|
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
|
|
@@ -133,6 +133,8 @@ autotouch rows get --table-id "$TABLE_ID" --row-id "$ROW_ID" --output json
|
|
|
133
133
|
- Inspect one cell: `autotouch cells get`
|
|
134
134
|
- Create a workflow column: `autotouch columns recipe`, `autotouch columns create`
|
|
135
135
|
- Run provider-hidden search: `autotouch search companies`, `autotouch search people`
|
|
136
|
+
- Inspect LinkedIn/Sales Nav filters: `autotouch linkedin filters --api sales_navigator --category people`
|
|
137
|
+
- Run one LinkedIn search page/debug replay: `autotouch linkedin search`
|
|
136
138
|
- Run controlled slices: `autotouch columns run-next`
|
|
137
139
|
- Poll authoritative state: `autotouch jobs get`
|
|
138
140
|
- Create/activate sequences: `autotouch sequences recipe`, `autotouch sequences create`, `autotouch sequences activate`
|
|
@@ -145,10 +147,17 @@ For automation or agent-driven setup, use:
|
|
|
145
147
|
- `autotouch cli-manifest --output json` for the local machine-readable command contract
|
|
146
148
|
- `autotouch cli-reference` for the shipped parser-generated reference
|
|
147
149
|
- `autotouch capabilities --output json` for provider/workflow contracts
|
|
150
|
+
- `autotouch linkedin filters --output json` for LinkedIn/Sales Navigator filter tokens such as headcount, industry, location, role, function, and seniority
|
|
148
151
|
- `autotouch rows list` / `autotouch rows get` / `autotouch cells get` for read-back and audit
|
|
149
152
|
- `autotouch sequences ...` and `autotouch tasks ...` for sequence/task workflows
|
|
150
153
|
- `pip install 'autotouch-cli[mongo]'` if you need the Mongo-backed `status` / `watch` commands
|
|
151
154
|
|
|
155
|
+
## Realtime Table Updates
|
|
156
|
+
|
|
157
|
+
Research-table cell updates are persistence-first. Writers update Mongo `cells`; the API-side Mongo change-stream listener emits table-scoped `cells_update_batch` events. Workers should not emit per-cell socket events directly, and `/api/events/emit` is not a cell-update transport.
|
|
158
|
+
|
|
159
|
+
CSV import/export and long-running worker flows still emit low-rate lifecycle/progress events such as `table_update`, but saved cell state is the source of truth for table rendering. For the full contract, see `docs/platform/realtime-events.md` and `docs/workers/bulk-jobs.md`.
|
|
160
|
+
|
|
152
161
|
## LLM Columns
|
|
153
162
|
|
|
154
163
|
For `llm_enrichment` in `agent` mode, the recommended path is:
|
|
@@ -175,3 +184,4 @@ Prompt variables in authored prompts support nested JSON access:
|
|
|
175
184
|
- Agent playbook: https://github.com/nicolonic/autotouch_main/blob/main/docs/research-table/guides/autotouch-cli-agent-playbook.md
|
|
176
185
|
- Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/main/docs/research-table/reference/tables-api.md
|
|
177
186
|
- Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/main/docs/platform/authentication.md
|
|
187
|
+
- Instantly/Smartlead external sending accounts: https://github.com/nicolonic/autotouch_main/blob/main/docs/integrations/email-automation-platforms.md
|
|
@@ -108,6 +108,8 @@ autotouch rows get --table-id "$TABLE_ID" --row-id "$ROW_ID" --output json
|
|
|
108
108
|
- Inspect one cell: `autotouch cells get`
|
|
109
109
|
- Create a workflow column: `autotouch columns recipe`, `autotouch columns create`
|
|
110
110
|
- Run provider-hidden search: `autotouch search companies`, `autotouch search people`
|
|
111
|
+
- Inspect LinkedIn/Sales Nav filters: `autotouch linkedin filters --api sales_navigator --category people`
|
|
112
|
+
- Run one LinkedIn search page/debug replay: `autotouch linkedin search`
|
|
111
113
|
- Run controlled slices: `autotouch columns run-next`
|
|
112
114
|
- Poll authoritative state: `autotouch jobs get`
|
|
113
115
|
- Create/activate sequences: `autotouch sequences recipe`, `autotouch sequences create`, `autotouch sequences activate`
|
|
@@ -120,10 +122,17 @@ For automation or agent-driven setup, use:
|
|
|
120
122
|
- `autotouch cli-manifest --output json` for the local machine-readable command contract
|
|
121
123
|
- `autotouch cli-reference` for the shipped parser-generated reference
|
|
122
124
|
- `autotouch capabilities --output json` for provider/workflow contracts
|
|
125
|
+
- `autotouch linkedin filters --output json` for LinkedIn/Sales Navigator filter tokens such as headcount, industry, location, role, function, and seniority
|
|
123
126
|
- `autotouch rows list` / `autotouch rows get` / `autotouch cells get` for read-back and audit
|
|
124
127
|
- `autotouch sequences ...` and `autotouch tasks ...` for sequence/task workflows
|
|
125
128
|
- `pip install 'autotouch-cli[mongo]'` if you need the Mongo-backed `status` / `watch` commands
|
|
126
129
|
|
|
130
|
+
## Realtime Table Updates
|
|
131
|
+
|
|
132
|
+
Research-table cell updates are persistence-first. Writers update Mongo `cells`; the API-side Mongo change-stream listener emits table-scoped `cells_update_batch` events. Workers should not emit per-cell socket events directly, and `/api/events/emit` is not a cell-update transport.
|
|
133
|
+
|
|
134
|
+
CSV import/export and long-running worker flows still emit low-rate lifecycle/progress events such as `table_update`, but saved cell state is the source of truth for table rendering. For the full contract, see `docs/platform/realtime-events.md` and `docs/workers/bulk-jobs.md`.
|
|
135
|
+
|
|
127
136
|
## LLM Columns
|
|
128
137
|
|
|
129
138
|
For `llm_enrichment` in `agent` mode, the recommended path is:
|
|
@@ -150,3 +159,4 @@ Prompt variables in authored prompts support nested JSON access:
|
|
|
150
159
|
- Agent playbook: https://github.com/nicolonic/autotouch_main/blob/main/docs/research-table/guides/autotouch-cli-agent-playbook.md
|
|
151
160
|
- Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/main/docs/research-table/reference/tables-api.md
|
|
152
161
|
- Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/main/docs/platform/authentication.md
|
|
162
|
+
- Instantly/Smartlead external sending accounts: https://github.com/nicolonic/autotouch_main/blob/main/docs/integrations/email-automation-platforms.md
|
|
@@ -93,6 +93,7 @@ from autotouch_cli.commands.leads import (
|
|
|
93
93
|
from autotouch_cli.commands.linkedin import (
|
|
94
94
|
LinkedInCommandRuntime as LinkedInCommandHandlerRuntime,
|
|
95
95
|
cmd_linkedin_comments as cmd_linkedin_comments_impl,
|
|
96
|
+
cmd_linkedin_filters as cmd_linkedin_filters_impl,
|
|
96
97
|
cmd_linkedin_limits as cmd_linkedin_limits_impl,
|
|
97
98
|
cmd_linkedin_post as cmd_linkedin_post_impl,
|
|
98
99
|
cmd_linkedin_posts as cmd_linkedin_posts_impl,
|
|
@@ -1544,6 +1545,7 @@ _register("linkedin_status", cmd_linkedin_status_impl, _linkedin_command_runtime
|
|
|
1544
1545
|
_register("linkedin_limits", cmd_linkedin_limits_impl, _linkedin_command_runtime)
|
|
1545
1546
|
_register("linkedin_search", cmd_linkedin_search_impl, _linkedin_command_runtime)
|
|
1546
1547
|
_register("linkedin_search_params", cmd_linkedin_search_params_impl, _linkedin_command_runtime)
|
|
1548
|
+
_register("linkedin_filters", cmd_linkedin_filters_impl, _linkedin_command_runtime)
|
|
1547
1549
|
_register("linkedin_posts", cmd_linkedin_posts_impl, _linkedin_command_runtime)
|
|
1548
1550
|
_register("linkedin_post", cmd_linkedin_post_impl, _linkedin_command_runtime)
|
|
1549
1551
|
_register("linkedin_comments", cmd_linkedin_comments_impl, _linkedin_command_runtime)
|
|
@@ -1962,6 +1964,9 @@ def cmd_capabilities(args: argparse.Namespace) -> None:
|
|
|
1962
1964
|
print("Not for :")
|
|
1963
1965
|
for item in not_for:
|
|
1964
1966
|
print(f" - {item}")
|
|
1967
|
+
filter_count = len((linkedin.get("filter_catalog") or {}).get("filters") or [])
|
|
1968
|
+
if filter_count:
|
|
1969
|
+
print(f"Structured filters: {filter_count} exposed via `autotouch linkedin filters`")
|
|
1965
1970
|
print("Tip: run `autotouch linkedin recipe` for example payloads.")
|
|
1966
1971
|
if isinstance(search, dict):
|
|
1967
1972
|
print("")
|
|
@@ -59,15 +59,47 @@ def cmd_agents_create(args: argparse.Namespace, *, runtime: AgentCommandRuntime)
|
|
|
59
59
|
payload["targetType"] = args.target
|
|
60
60
|
if args.assigned_user:
|
|
61
61
|
payload["assignedUserId"] = args.assigned_user
|
|
62
|
+
# Build the schedule object from any provided schedule flags. We only
|
|
63
|
+
# send a `schedule` key when the user actually set at least one field.
|
|
64
|
+
schedule_payload: Dict[str, Any] = {}
|
|
62
65
|
if args.schedule_time:
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
schedule_payload["timeOfDay"] = args.schedule_time
|
|
67
|
+
schedule_tz = getattr(args, "schedule_tz", None)
|
|
68
|
+
if schedule_tz:
|
|
69
|
+
schedule_payload["timezone"] = schedule_tz
|
|
70
|
+
schedule_frequency = getattr(args, "schedule_frequency", None)
|
|
71
|
+
if schedule_frequency:
|
|
72
|
+
schedule_payload["frequency"] = schedule_frequency
|
|
73
|
+
schedule_days = getattr(args, "schedule_days", None)
|
|
74
|
+
if schedule_days:
|
|
75
|
+
schedule_payload["days"] = schedule_days
|
|
76
|
+
schedule_day_of_week = getattr(args, "schedule_day_of_week", None)
|
|
77
|
+
if schedule_day_of_week is not None:
|
|
78
|
+
schedule_payload["dayOfWeek"] = schedule_day_of_week
|
|
79
|
+
schedule_window_start = getattr(args, "schedule_window_start", None)
|
|
80
|
+
if schedule_window_start:
|
|
81
|
+
schedule_payload["windowStart"] = schedule_window_start
|
|
82
|
+
schedule_window_end = getattr(args, "schedule_window_end", None)
|
|
83
|
+
if schedule_window_end:
|
|
84
|
+
schedule_payload["windowEnd"] = schedule_window_end
|
|
85
|
+
if schedule_payload:
|
|
86
|
+
# Backfill required fields the API expects (timezone defaults to UTC,
|
|
87
|
+
# timeOfDay defaults to 09:00 — required even for sub-daily frequencies).
|
|
88
|
+
schedule_payload.setdefault("timezone", "UTC")
|
|
89
|
+
schedule_payload.setdefault("timeOfDay", "09:00")
|
|
90
|
+
payload["schedule"] = schedule_payload
|
|
65
91
|
if getattr(args, "auto_find_email", False):
|
|
66
92
|
payload["autoFindEmail"] = True
|
|
67
93
|
if getattr(args, "auto_find_phone", False):
|
|
68
94
|
payload["autoFindPhone"] = True
|
|
69
95
|
if getattr(args, "auto_broaden", False):
|
|
70
96
|
payload["autoBroadenSearch"] = True
|
|
97
|
+
auto_create_lead_mode = getattr(args, "auto_create_lead_mode", None)
|
|
98
|
+
if auto_create_lead_mode:
|
|
99
|
+
payload["autoCreateLeadMode"] = auto_create_lead_mode
|
|
100
|
+
auto_sequence_id = getattr(args, "auto_sequence_id", None)
|
|
101
|
+
if auto_sequence_id:
|
|
102
|
+
payload["autoSequenceId"] = auto_sequence_id
|
|
71
103
|
status = getattr(args, "status", None)
|
|
72
104
|
if status:
|
|
73
105
|
payload["status"] = status
|
|
@@ -165,6 +197,47 @@ def cmd_agents_update(args: argparse.Namespace, *, runtime: AgentCommandRuntime)
|
|
|
165
197
|
payload["autoFindPhone"] = args.auto_find_phone
|
|
166
198
|
if getattr(args, "auto_broaden", None) is not None:
|
|
167
199
|
payload["autoBroadenSearch"] = args.auto_broaden
|
|
200
|
+
if getattr(args, "auto_create_lead_mode", None):
|
|
201
|
+
payload["autoCreateLeadMode"] = args.auto_create_lead_mode
|
|
202
|
+
# Pass an empty string to clear the auto-sequence link.
|
|
203
|
+
auto_sequence_id_arg = getattr(args, "auto_sequence_id", None)
|
|
204
|
+
if auto_sequence_id_arg is not None:
|
|
205
|
+
payload["autoSequenceId"] = auto_sequence_id_arg or None
|
|
206
|
+
|
|
207
|
+
# Schedule changes need a full schedule object, so we merge any
|
|
208
|
+
# provided flags onto the agent's existing schedule.
|
|
209
|
+
schedule_flag_values = {
|
|
210
|
+
"timeOfDay": getattr(args, "schedule_time", None),
|
|
211
|
+
"timezone": getattr(args, "schedule_tz", None),
|
|
212
|
+
"frequency": getattr(args, "schedule_frequency", None),
|
|
213
|
+
"days": getattr(args, "schedule_days", None),
|
|
214
|
+
"dayOfWeek": getattr(args, "schedule_day_of_week", None),
|
|
215
|
+
"windowStart": getattr(args, "schedule_window_start", None),
|
|
216
|
+
"windowEnd": getattr(args, "schedule_window_end", None),
|
|
217
|
+
}
|
|
218
|
+
if any(v is not None for v in schedule_flag_values.values()):
|
|
219
|
+
existing = runtime.request_api(
|
|
220
|
+
"GET", f"/api/agents/{args.agent_id}",
|
|
221
|
+
base_url=args.base_url, token=token,
|
|
222
|
+
use_x_api_key=args.use_x_api_key,
|
|
223
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
224
|
+
)
|
|
225
|
+
existing_schedule = (existing or {}).get("schedule") or {}
|
|
226
|
+
merged_schedule: Dict[str, Any] = {
|
|
227
|
+
"timezone": existing_schedule.get("timezone") or "UTC",
|
|
228
|
+
"timeOfDay": existing_schedule.get("timeOfDay") or "09:00",
|
|
229
|
+
"frequency": existing_schedule.get("frequency") or "daily",
|
|
230
|
+
"days": existing_schedule.get("days") or "all",
|
|
231
|
+
"dayOfWeek": existing_schedule.get("dayOfWeek") or 0,
|
|
232
|
+
}
|
|
233
|
+
if existing_schedule.get("windowStart"):
|
|
234
|
+
merged_schedule["windowStart"] = existing_schedule["windowStart"]
|
|
235
|
+
if existing_schedule.get("windowEnd"):
|
|
236
|
+
merged_schedule["windowEnd"] = existing_schedule["windowEnd"]
|
|
237
|
+
for key, value in schedule_flag_values.items():
|
|
238
|
+
if value is not None:
|
|
239
|
+
merged_schedule[key] = value
|
|
240
|
+
payload["schedule"] = merged_schedule
|
|
168
241
|
|
|
169
242
|
data = runtime.request_api(
|
|
170
243
|
"PATCH", f"/api/agents/{args.agent_id}",
|
|
@@ -376,11 +449,52 @@ def register_agents_subcommands(
|
|
|
376
449
|
pac.add_argument("--name", help="Agent name")
|
|
377
450
|
pac.add_argument("--target", choices=["LEADS"], help="Target type")
|
|
378
451
|
pac.add_argument("--assigned-user", dest="assigned_user", help="Assigned user ID")
|
|
379
|
-
pac.add_argument("--schedule-time", dest="schedule_time", help="Time of day (HH:MM)")
|
|
452
|
+
pac.add_argument("--schedule-time", dest="schedule_time", help="Time of day (HH:MM) — used by daily/weekly")
|
|
380
453
|
pac.add_argument("--schedule-tz", dest="schedule_tz", help="Timezone (default: UTC)")
|
|
454
|
+
pac.add_argument(
|
|
455
|
+
"--schedule-frequency",
|
|
456
|
+
dest="schedule_frequency",
|
|
457
|
+
choices=["hourly", "every_6h", "every_12h", "daily", "weekly"],
|
|
458
|
+
help="Run cadence (default: daily)",
|
|
459
|
+
)
|
|
460
|
+
pac.add_argument(
|
|
461
|
+
"--schedule-days",
|
|
462
|
+
dest="schedule_days",
|
|
463
|
+
choices=["all", "weekdays"],
|
|
464
|
+
help="Which days to run on for sub-weekly cadences (default: all)",
|
|
465
|
+
)
|
|
466
|
+
pac.add_argument(
|
|
467
|
+
"--schedule-day-of-week",
|
|
468
|
+
dest="schedule_day_of_week",
|
|
469
|
+
type=int,
|
|
470
|
+
choices=range(0, 7),
|
|
471
|
+
metavar="0-6",
|
|
472
|
+
help="Day of week for weekly cadence (0=Mon, 6=Sun)",
|
|
473
|
+
)
|
|
474
|
+
pac.add_argument(
|
|
475
|
+
"--schedule-window-start",
|
|
476
|
+
dest="schedule_window_start",
|
|
477
|
+
help="Active-hours window start (HH:MM) — used by hourly/every_6h/every_12h",
|
|
478
|
+
)
|
|
479
|
+
pac.add_argument(
|
|
480
|
+
"--schedule-window-end",
|
|
481
|
+
dest="schedule_window_end",
|
|
482
|
+
help="Active-hours window end (HH:MM) — used by hourly/every_6h/every_12h",
|
|
483
|
+
)
|
|
381
484
|
pac.add_argument("--auto-find-email", dest="auto_find_email", action="store_true", default=False, help="Enable auto email finder")
|
|
382
485
|
pac.add_argument("--auto-find-phone", dest="auto_find_phone", action="store_true", default=False, help="Enable auto phone finder")
|
|
383
486
|
pac.add_argument("--auto-broaden", dest="auto_broaden", action="store_true", default=False, help="Enable auto broaden search")
|
|
487
|
+
pac.add_argument(
|
|
488
|
+
"--auto-create-lead-mode",
|
|
489
|
+
dest="auto_create_lead_mode",
|
|
490
|
+
choices=["qualified_only", "everyone", "manual_only"],
|
|
491
|
+
help="Which discovered prospects become leads (default: qualified_only)",
|
|
492
|
+
)
|
|
493
|
+
pac.add_argument(
|
|
494
|
+
"--auto-sequence-id",
|
|
495
|
+
dest="auto_sequence_id",
|
|
496
|
+
help="Sequence ID — discovered leads get auto-enrolled. Triggers outreach automatically.",
|
|
497
|
+
)
|
|
384
498
|
pac.add_argument("--status", choices=["DRAFT", "ACTIVE", "PAUSED"], help="Initial status")
|
|
385
499
|
pac.add_argument("--from-file", dest="from_file", help="Path to JSON payload file")
|
|
386
500
|
pac.add_argument("--generate", action="store_true", help="Auto-generate persona, topics, and job signal config")
|
|
@@ -395,6 +509,49 @@ def register_agents_subcommands(
|
|
|
395
509
|
pau.add_argument("--auto-find-email", dest="auto_find_email", type=_bool_arg, default=None, help="Enable/disable auto email finder")
|
|
396
510
|
pau.add_argument("--auto-find-phone", dest="auto_find_phone", type=_bool_arg, default=None, help="Enable/disable auto phone finder")
|
|
397
511
|
pau.add_argument("--auto-broaden", dest="auto_broaden", type=_bool_arg, default=None, help="Enable/disable auto broaden search")
|
|
512
|
+
pau.add_argument(
|
|
513
|
+
"--auto-create-lead-mode",
|
|
514
|
+
dest="auto_create_lead_mode",
|
|
515
|
+
choices=["qualified_only", "everyone", "manual_only"],
|
|
516
|
+
help="Which discovered prospects become leads",
|
|
517
|
+
)
|
|
518
|
+
pau.add_argument(
|
|
519
|
+
"--auto-sequence-id",
|
|
520
|
+
dest="auto_sequence_id",
|
|
521
|
+
help="Sequence ID for auto-enrollment. Pass empty string to clear.",
|
|
522
|
+
)
|
|
523
|
+
pau.add_argument("--schedule-time", dest="schedule_time", help="Time of day (HH:MM) — used by daily/weekly")
|
|
524
|
+
pau.add_argument("--schedule-tz", dest="schedule_tz", help="Timezone")
|
|
525
|
+
pau.add_argument(
|
|
526
|
+
"--schedule-frequency",
|
|
527
|
+
dest="schedule_frequency",
|
|
528
|
+
choices=["hourly", "every_6h", "every_12h", "daily", "weekly"],
|
|
529
|
+
help="Run cadence",
|
|
530
|
+
)
|
|
531
|
+
pau.add_argument(
|
|
532
|
+
"--schedule-days",
|
|
533
|
+
dest="schedule_days",
|
|
534
|
+
choices=["all", "weekdays"],
|
|
535
|
+
help="Which days to run on for sub-weekly cadences",
|
|
536
|
+
)
|
|
537
|
+
pau.add_argument(
|
|
538
|
+
"--schedule-day-of-week",
|
|
539
|
+
dest="schedule_day_of_week",
|
|
540
|
+
type=int,
|
|
541
|
+
choices=range(0, 7),
|
|
542
|
+
metavar="0-6",
|
|
543
|
+
help="Day of week for weekly cadence (0=Mon, 6=Sun)",
|
|
544
|
+
)
|
|
545
|
+
pau.add_argument(
|
|
546
|
+
"--schedule-window-start",
|
|
547
|
+
dest="schedule_window_start",
|
|
548
|
+
help="Active-hours window start (HH:MM) — used by hourly/every_6h/every_12h",
|
|
549
|
+
)
|
|
550
|
+
pau.add_argument(
|
|
551
|
+
"--schedule-window-end",
|
|
552
|
+
dest="schedule_window_end",
|
|
553
|
+
help="Active-hours window end (HH:MM) — used by hourly/every_6h/every_12h",
|
|
554
|
+
)
|
|
398
555
|
pau.add_argument("--from-file", dest="from_file", help="Path to JSON patch payload file")
|
|
399
556
|
add_api_common_arguments(pau)
|
|
400
557
|
pau.set_defaults(func=handlers["update"])
|
|
@@ -5,6 +5,7 @@ import json
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import Any, Callable, Dict, Optional, Sequence
|
|
7
7
|
|
|
8
|
+
from autotouch_shared.linkedin_filters import linkedin_filter_catalog
|
|
8
9
|
from autotouch_cli.exceptions import AutotouchInputError
|
|
9
10
|
|
|
10
11
|
|
|
@@ -66,8 +67,8 @@ def _normalize_linkedin_search_payload(args: argparse.Namespace, *, runtime: Lin
|
|
|
66
67
|
keywords = str(getattr(args, "keywords", "") or "").strip()
|
|
67
68
|
if keywords:
|
|
68
69
|
payload["keywords"] = keywords
|
|
69
|
-
linkedin_api = str(getattr(args, "linkedin_api", "
|
|
70
|
-
if linkedin_api:
|
|
70
|
+
linkedin_api = str(getattr(args, "linkedin_api", "auto") or "auto").strip()
|
|
71
|
+
if linkedin_api and linkedin_api != "auto":
|
|
71
72
|
payload["linkedin_api"] = linkedin_api
|
|
72
73
|
cursor = str(getattr(args, "cursor", "") or "").strip()
|
|
73
74
|
if cursor:
|
|
@@ -101,6 +102,9 @@ def cmd_linkedin_search_params(args: argparse.Namespace, *, runtime: LinkedInCom
|
|
|
101
102
|
"keywords": str(getattr(args, "keywords", "") or "").strip(),
|
|
102
103
|
"limit": max(1, min(int(getattr(args, "limit", 25) or 25), 250)),
|
|
103
104
|
}
|
|
105
|
+
linkedin_api = str(getattr(args, "linkedin_api", "auto") or "auto").strip()
|
|
106
|
+
if linkedin_api and linkedin_api != "auto":
|
|
107
|
+
payload["linkedin_api"] = linkedin_api
|
|
104
108
|
data = runtime.request_api(
|
|
105
109
|
"POST",
|
|
106
110
|
"/api/integrations/linkedin/search/parameters",
|
|
@@ -114,6 +118,19 @@ def cmd_linkedin_search_params(args: argparse.Namespace, *, runtime: LinkedInCom
|
|
|
114
118
|
runtime.print_json(data, args.compact)
|
|
115
119
|
|
|
116
120
|
|
|
121
|
+
def cmd_linkedin_filters(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
|
|
122
|
+
output = linkedin_filter_catalog(
|
|
123
|
+
api_mode=getattr(args, "api", None),
|
|
124
|
+
category=getattr(args, "category", None),
|
|
125
|
+
field=getattr(args, "field", None),
|
|
126
|
+
)
|
|
127
|
+
out_file = str(getattr(args, "out_file", "") or "").strip()
|
|
128
|
+
if out_file:
|
|
129
|
+
with open(out_file, "w", encoding="utf-8") as f:
|
|
130
|
+
json.dump(output, f, indent=2)
|
|
131
|
+
runtime.print_json(output, args.compact)
|
|
132
|
+
|
|
133
|
+
|
|
117
134
|
def cmd_linkedin_recipe(
|
|
118
135
|
args: argparse.Namespace,
|
|
119
136
|
*,
|
|
@@ -253,7 +270,7 @@ def register_linkedin_subcommands(
|
|
|
253
270
|
search_parser.add_argument("--url", help="Full LinkedIn/Sales Navigator search URL (overrides keyword filters)")
|
|
254
271
|
search_parser.add_argument("--category", choices=["people", "companies", "jobs", "posts"], default="people", help="Search category (default: people)")
|
|
255
272
|
search_parser.add_argument("--keywords", help="Free-text keyword search")
|
|
256
|
-
search_parser.add_argument("--linkedin-api", choices=["classic", "sales_navigator"], default="
|
|
273
|
+
search_parser.add_argument("--linkedin-api", choices=["auto", "classic", "sales_navigator"], default="auto", help="API mode (default: auto; uses connected account mode)")
|
|
257
274
|
search_parser.add_argument("--cursor", help="Pagination cursor from previous response")
|
|
258
275
|
search_parser.add_argument("--limit", type=int, default=25, help="Results per page, 1-250 (default: 25)")
|
|
259
276
|
search_parser.add_argument("--extra-params-json", help="JSON object with additional search filters (location IDs, industry, etc.)")
|
|
@@ -263,10 +280,19 @@ def register_linkedin_subcommands(
|
|
|
263
280
|
params_parser = linkedin_sub.add_parser("search-params", help="Resolve names to LinkedIn parameter IDs for search filters")
|
|
264
281
|
params_parser.add_argument("--type", required=True, help="Parameter type: location, industry, company, school, title, language, etc.")
|
|
265
282
|
params_parser.add_argument("--keywords", default="", help="Search text to match")
|
|
283
|
+
params_parser.add_argument("--linkedin-api", choices=["auto", "classic", "sales_navigator"], default="auto", help="Resolver service (default: auto; uses connected account mode)")
|
|
266
284
|
params_parser.add_argument("--limit", type=int, default=25, help="Max results, 1-250 (default: 25)")
|
|
267
285
|
add_api_common_arguments(params_parser)
|
|
268
286
|
params_parser.set_defaults(func=handlers["search_params"])
|
|
269
287
|
|
|
288
|
+
filters_parser = linkedin_sub.add_parser("filters", help="Print the LinkedIn/Sales Navigator structured filter catalog")
|
|
289
|
+
filters_parser.add_argument("--api", choices=["all", "classic", "sales_navigator"], default="all", help="Filter by API mode")
|
|
290
|
+
filters_parser.add_argument("--category", choices=["all", "people", "companies", "jobs", "posts"], default="all", help="Filter by search category")
|
|
291
|
+
filters_parser.add_argument("--field", help="Show one filter field or alias, e.g. headcount, company_headcount, role")
|
|
292
|
+
filters_parser.add_argument("--out-file", help="Optional path to save the selected catalog JSON")
|
|
293
|
+
add_api_common_arguments(filters_parser)
|
|
294
|
+
filters_parser.set_defaults(func=handlers["filters"])
|
|
295
|
+
|
|
270
296
|
posts_parser = linkedin_sub.add_parser("posts", help="List LinkedIn posts for a user")
|
|
271
297
|
posts_parser.add_argument("--identifier", required=True, help="LinkedIn profile URL, public ID, or URN")
|
|
272
298
|
posts_parser.add_argument("--cursor", help="Pagination cursor from previous response")
|
|
@@ -47,7 +47,19 @@ def _decode_response_body(response: Any) -> Any:
|
|
|
47
47
|
return response.text
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def _print_special_error_hint(body: Any) -> None:
|
|
51
|
+
detail = body.get("detail") if isinstance(body, dict) else None
|
|
52
|
+
if isinstance(detail, dict) and detail.get("error_code") == "sales_nav_reconnect_required":
|
|
53
|
+
message = detail.get("message") or "Sales Navigator search credentials expired."
|
|
54
|
+
print(f"ERROR: {message}", file=sys.stderr)
|
|
55
|
+
print(
|
|
56
|
+
"Open Autotouch > Settings > Inbox > LinkedIn > Reconnect Sales Navigator.",
|
|
57
|
+
file=sys.stderr,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
50
61
|
def _print_error_body(body: Any) -> None:
|
|
62
|
+
_print_special_error_hint(body)
|
|
51
63
|
if isinstance(body, (dict, list)):
|
|
52
64
|
print(json.dumps(body, indent=2, default=str), file=sys.stderr)
|
|
53
65
|
else:
|
|
@@ -129,6 +141,7 @@ def request_api(
|
|
|
129
141
|
)
|
|
130
142
|
print(f"ERROR: API {response.status_code}", file=sys.stderr)
|
|
131
143
|
if error_body_writer is not None:
|
|
144
|
+
_print_special_error_hint(body)
|
|
132
145
|
error_body_writer(body)
|
|
133
146
|
else:
|
|
134
147
|
_print_error_body(body)
|
|
@@ -212,6 +225,7 @@ def request_multipart_api(
|
|
|
212
225
|
)
|
|
213
226
|
print(f"ERROR: API {response.status_code}", file=sys.stderr)
|
|
214
227
|
if error_body_writer is not None:
|
|
228
|
+
_print_special_error_hint(body)
|
|
215
229
|
error_body_writer(body)
|
|
216
230
|
else:
|
|
217
231
|
_print_error_body(body)
|