autotouch-cli 0.2.60__tar.gz → 0.2.63__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.60 → autotouch_cli-0.2.63}/PKG-INFO +6 -1
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/README.md +5 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/cli.py +40 -0
- autotouch_cli-0.2.63/autotouch_cli/commands/agents.py +465 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/columns.py +16 -1
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/data/CLI_REFERENCE.md +15 -1
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/data/cli-manifest.json +3 -3
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/parser.py +21 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli.egg-info/PKG-INFO +6 -1
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli.egg-info/SOURCES.txt +1 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_shared/provider_registry.py +53 -12
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/pyproject.toml +1 -1
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/MANIFEST.in +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/__init__.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/cli_contracts.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/__init__.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/auth.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/cells.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/jobs.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/leads.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/linkedin.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/prompts.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/rows.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/search.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/sequences.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/tables.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/tasks.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/webhooks.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/commands/workspace_secrets.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/core/__init__.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/core/auth.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/core/config.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/core/csv_import.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/core/http.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/core/io.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/core/output.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/core/polling.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/core/run.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/core/validation.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/exceptions.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/mongo_status.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/parser_groups.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/sequence_support.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli/templates.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli.egg-info/dependency_links.txt +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli.egg-info/entry_points.txt +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli.egg-info/requires.txt +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_cli.egg-info/top_level.txt +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_shared/__init__.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_shared/linkedin_contract.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/autotouch_shared/search_contract.py +0 -0
- {autotouch_cli-0.2.60 → autotouch_cli-0.2.63}/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.63
|
|
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
|
|
@@ -158,6 +158,11 @@ For `llm_enrichment` in `agent` mode, the recommended path is:
|
|
|
158
158
|
|
|
159
159
|
Only send `user_schema` / `response_schema` when you intentionally want to override the generated schema and keep it aligned yourself. The installed recipe surface at `autotouch columns recipe --type llm_enrichment` follows this contract.
|
|
160
160
|
|
|
161
|
+
Schema ownership rules:
|
|
162
|
+
- Accepted generated schemas and explicit user schemas are the saved output contract.
|
|
163
|
+
- Row execution must not add fields, rename fields, or replace a valid locked schema.
|
|
164
|
+
- Agent-mode evidence/scored state decides which values may fill the schema; the finalizer formats those values and schema validation gates persistence.
|
|
165
|
+
|
|
161
166
|
Prompt variables in authored prompts support nested JSON access:
|
|
162
167
|
- Use flat row variables like `{{company_name}}` for scalar columns.
|
|
163
168
|
- Use dotted placeholders like `{{linkedin_lookup.linkedin_url}}` when the source column stores JSON or stringified JSON.
|
|
@@ -133,6 +133,11 @@ For `llm_enrichment` in `agent` mode, the recommended path is:
|
|
|
133
133
|
|
|
134
134
|
Only send `user_schema` / `response_schema` when you intentionally want to override the generated schema and keep it aligned yourself. The installed recipe surface at `autotouch columns recipe --type llm_enrichment` follows this contract.
|
|
135
135
|
|
|
136
|
+
Schema ownership rules:
|
|
137
|
+
- Accepted generated schemas and explicit user schemas are the saved output contract.
|
|
138
|
+
- Row execution must not add fields, rename fields, or replace a valid locked schema.
|
|
139
|
+
- Agent-mode evidence/scored state decides which values may fill the schema; the finalizer formats those values and schema validation gates persistence.
|
|
140
|
+
|
|
136
141
|
Prompt variables in authored prompts support nested JSON access:
|
|
137
142
|
- Use flat row variables like `{{company_name}}` for scalar columns.
|
|
138
143
|
- Use dotted placeholders like `{{linkedin_lookup.linkedin_url}}` when the source column stores JSON or stringified JSON.
|
|
@@ -125,6 +125,21 @@ from autotouch_cli.commands.search import (
|
|
|
125
125
|
cmd_search_reviews as cmd_search_reviews_impl,
|
|
126
126
|
cmd_search_similar_companies as cmd_search_similar_companies_impl,
|
|
127
127
|
)
|
|
128
|
+
from autotouch_cli.commands.agents import (
|
|
129
|
+
AgentCommandRuntime as AgentCommandHandlerRuntime,
|
|
130
|
+
cmd_agents_list as cmd_agents_list_impl,
|
|
131
|
+
cmd_agents_get as cmd_agents_get_impl,
|
|
132
|
+
cmd_agents_create as cmd_agents_create_impl,
|
|
133
|
+
cmd_agents_update as cmd_agents_update_impl,
|
|
134
|
+
cmd_agents_delete as cmd_agents_delete_impl,
|
|
135
|
+
cmd_agents_run as cmd_agents_run_impl,
|
|
136
|
+
cmd_agents_runs as cmd_agents_runs_impl,
|
|
137
|
+
cmd_agents_watch as cmd_agents_watch_impl,
|
|
138
|
+
cmd_agents_generate_persona as cmd_agents_generate_persona_impl,
|
|
139
|
+
cmd_agents_generate_topics as cmd_agents_generate_topics_impl,
|
|
140
|
+
cmd_agents_generate_job_signal as cmd_agents_generate_job_signal_impl,
|
|
141
|
+
cmd_agents_signals as cmd_agents_signals_impl,
|
|
142
|
+
)
|
|
128
143
|
from autotouch_cli.commands.sequences import (
|
|
129
144
|
SequenceCommandRuntime as SequenceCommandHandlerRuntime,
|
|
130
145
|
cmd_sequences_clone as cmd_sequences_clone_impl,
|
|
@@ -1164,6 +1179,17 @@ def _sequence_command_runtime() -> SequenceCommandHandlerRuntime:
|
|
|
1164
1179
|
)
|
|
1165
1180
|
|
|
1166
1181
|
|
|
1182
|
+
def _agent_command_runtime() -> AgentCommandHandlerRuntime:
|
|
1183
|
+
return AgentCommandHandlerRuntime(
|
|
1184
|
+
resolve_token=_resolve_token,
|
|
1185
|
+
request_api=_request_api,
|
|
1186
|
+
print_json=lambda data, compact=False: _print_json(data, compact=compact),
|
|
1187
|
+
load_json_input=_load_json_input,
|
|
1188
|
+
api_url=_api_url,
|
|
1189
|
+
default_timeout_seconds=DEFAULT_TIMEOUT_SECONDS,
|
|
1190
|
+
)
|
|
1191
|
+
|
|
1192
|
+
|
|
1167
1193
|
def _column_command_runtime() -> ColumnCommandHandlerRuntime:
|
|
1168
1194
|
return ColumnCommandHandlerRuntime(
|
|
1169
1195
|
resolve_token=_resolve_token,
|
|
@@ -1420,6 +1446,20 @@ _register("columns_run_next", cmd_columns_run_next_impl, _column_command_runtime
|
|
|
1420
1446
|
_register("columns_stop", cmd_columns_stop_impl, _column_command_runtime)
|
|
1421
1447
|
_register("columns_estimate", cmd_columns_estimate_impl, _column_command_runtime)
|
|
1422
1448
|
|
|
1449
|
+
# -- Agent commands --
|
|
1450
|
+
_register("agents_list", cmd_agents_list_impl, _agent_command_runtime)
|
|
1451
|
+
_register("agents_get", cmd_agents_get_impl, _agent_command_runtime)
|
|
1452
|
+
_register("agents_create", cmd_agents_create_impl, _agent_command_runtime)
|
|
1453
|
+
_register("agents_update", cmd_agents_update_impl, _agent_command_runtime)
|
|
1454
|
+
_register("agents_delete", cmd_agents_delete_impl, _agent_command_runtime)
|
|
1455
|
+
_register("agents_run", cmd_agents_run_impl, _agent_command_runtime)
|
|
1456
|
+
_register("agents_runs", cmd_agents_runs_impl, _agent_command_runtime)
|
|
1457
|
+
_register("agents_watch", cmd_agents_watch_impl, _agent_command_runtime)
|
|
1458
|
+
_register("agents_generate_persona", cmd_agents_generate_persona_impl, _agent_command_runtime)
|
|
1459
|
+
_register("agents_generate_topics", cmd_agents_generate_topics_impl, _agent_command_runtime)
|
|
1460
|
+
_register("agents_generate_job_signal", cmd_agents_generate_job_signal_impl, _agent_command_runtime)
|
|
1461
|
+
_register("agents_signals", cmd_agents_signals_impl, _agent_command_runtime)
|
|
1462
|
+
|
|
1423
1463
|
# -- Sequence commands --
|
|
1424
1464
|
_register("sequences_list", cmd_sequences_list_impl, _sequence_command_runtime)
|
|
1425
1465
|
_register("sequences_get", cmd_sequences_get_impl, _sequence_command_runtime)
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
"""Scheduled agent CLI commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any, Callable, Dict, Optional, Sequence
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class AgentCommandRuntime:
|
|
15
|
+
resolve_token: Callable[[Optional[str], bool], Optional[str]]
|
|
16
|
+
request_api: Callable[..., Any]
|
|
17
|
+
print_json: Callable[[Any, bool], None]
|
|
18
|
+
load_json_input: Callable[..., Any]
|
|
19
|
+
api_url: Callable[[Optional[str]], str]
|
|
20
|
+
default_timeout_seconds: int
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# CRUD
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
def cmd_agents_list(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
28
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
29
|
+
data = runtime.request_api(
|
|
30
|
+
"GET", "/api/agents",
|
|
31
|
+
base_url=args.base_url, token=token,
|
|
32
|
+
use_x_api_key=args.use_x_api_key,
|
|
33
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
34
|
+
)
|
|
35
|
+
runtime.print_json(data, args.compact)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cmd_agents_get(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
39
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
40
|
+
data = runtime.request_api(
|
|
41
|
+
"GET", f"/api/agents/{args.agent_id}",
|
|
42
|
+
base_url=args.base_url, token=token,
|
|
43
|
+
use_x_api_key=args.use_x_api_key,
|
|
44
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
45
|
+
)
|
|
46
|
+
runtime.print_json(data, args.compact)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cmd_agents_create(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
50
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
51
|
+
|
|
52
|
+
if getattr(args, "from_file", None):
|
|
53
|
+
payload = runtime.load_json_input(args.from_file)
|
|
54
|
+
else:
|
|
55
|
+
payload: Dict[str, Any] = {}
|
|
56
|
+
if args.name:
|
|
57
|
+
payload["name"] = args.name
|
|
58
|
+
if args.target:
|
|
59
|
+
payload["targetType"] = args.target
|
|
60
|
+
if args.assigned_user:
|
|
61
|
+
payload["assignedUserId"] = args.assigned_user
|
|
62
|
+
if args.schedule_time:
|
|
63
|
+
tz = getattr(args, "schedule_tz", None) or "UTC"
|
|
64
|
+
payload["schedule"] = {"timezone": tz, "timeOfDay": args.schedule_time}
|
|
65
|
+
if getattr(args, "auto_find_email", False):
|
|
66
|
+
payload["autoFindEmail"] = True
|
|
67
|
+
if getattr(args, "auto_find_phone", False):
|
|
68
|
+
payload["autoFindPhone"] = True
|
|
69
|
+
if getattr(args, "auto_broaden", False):
|
|
70
|
+
payload["autoBroadenSearch"] = True
|
|
71
|
+
status = getattr(args, "status", None)
|
|
72
|
+
if status:
|
|
73
|
+
payload["status"] = status
|
|
74
|
+
|
|
75
|
+
# AI generation: generate persona, signals, and create in one shot
|
|
76
|
+
if getattr(args, "generate", False) and "personaConfig" not in payload:
|
|
77
|
+
target = payload.get("targetType", "LEADS")
|
|
78
|
+
sys.stderr.write(f"Generating persona for {target}...\n")
|
|
79
|
+
persona_data = runtime.request_api(
|
|
80
|
+
"POST", "/api/agents/generate-persona",
|
|
81
|
+
base_url=args.base_url, token=token,
|
|
82
|
+
use_x_api_key=args.use_x_api_key,
|
|
83
|
+
json={"targetType": target},
|
|
84
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
85
|
+
)
|
|
86
|
+
persona = persona_data.get("persona") or persona_data.get("personaConfig") or {}
|
|
87
|
+
payload["personaConfig"] = persona
|
|
88
|
+
sys.stderr.write(f" Persona generated: {json.dumps(persona, ensure_ascii=False)[:200]}...\n")
|
|
89
|
+
|
|
90
|
+
# Generate topics
|
|
91
|
+
sys.stderr.write("Generating topic suggestions...\n")
|
|
92
|
+
topic_data = runtime.request_api(
|
|
93
|
+
"POST", "/api/agents/generate-signal-topics",
|
|
94
|
+
base_url=args.base_url, token=token,
|
|
95
|
+
use_x_api_key=args.use_x_api_key,
|
|
96
|
+
json={"targetType": target, "personaConfig": persona},
|
|
97
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
98
|
+
)
|
|
99
|
+
topics = topic_data.get("topics") or []
|
|
100
|
+
sys.stderr.write(f" Topics: {topics}\n")
|
|
101
|
+
|
|
102
|
+
# Generate job signal
|
|
103
|
+
sys.stderr.write("Generating job signal config...\n")
|
|
104
|
+
job_data = runtime.request_api(
|
|
105
|
+
"POST", "/api/agents/generate-job-signal",
|
|
106
|
+
base_url=args.base_url, token=token,
|
|
107
|
+
use_x_api_key=args.use_x_api_key,
|
|
108
|
+
json={"targetType": target, "personaConfig": persona},
|
|
109
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
110
|
+
)
|
|
111
|
+
job_titles = job_data.get("jobTitles") or []
|
|
112
|
+
job_keywords = job_data.get("keywords") or []
|
|
113
|
+
sys.stderr.write(f" Job titles: {job_titles}\n")
|
|
114
|
+
|
|
115
|
+
# Assemble signals
|
|
116
|
+
signals = []
|
|
117
|
+
if topics:
|
|
118
|
+
signals.append({
|
|
119
|
+
"key": "linkedin-topic-post-engagement",
|
|
120
|
+
"config": {"topics": [{"id": "", "label": t, "trackingMode": "all"} for t in topics[:5]]},
|
|
121
|
+
})
|
|
122
|
+
if job_titles or job_keywords:
|
|
123
|
+
signals.append({
|
|
124
|
+
"key": "hiring-signals",
|
|
125
|
+
"config": {"jobTitles": job_titles, "keywords": job_keywords, "remoteOnly": False, "lookbackWindow": "7d"},
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
# News scan: derive buying trigger topics from persona
|
|
129
|
+
news_topics = ["raised funding", "new executive hire"]
|
|
130
|
+
industries = (persona.get("industries") or [])[:2]
|
|
131
|
+
if industries:
|
|
132
|
+
news_topics.append(f"expansion {industries[0]}")
|
|
133
|
+
signals.append({
|
|
134
|
+
"key": "news-scan",
|
|
135
|
+
"config": {"topics": news_topics, "lookback": "24h"},
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
if signals:
|
|
139
|
+
payload["selectedSignals"] = signals
|
|
140
|
+
|
|
141
|
+
data = runtime.request_api(
|
|
142
|
+
"POST", "/api/agents",
|
|
143
|
+
base_url=args.base_url, token=token,
|
|
144
|
+
use_x_api_key=args.use_x_api_key,
|
|
145
|
+
json=payload,
|
|
146
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
147
|
+
)
|
|
148
|
+
runtime.print_json(data, args.compact)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def cmd_agents_update(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
152
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
153
|
+
|
|
154
|
+
if getattr(args, "from_file", None):
|
|
155
|
+
payload = runtime.load_json_input(args.from_file)
|
|
156
|
+
else:
|
|
157
|
+
payload: Dict[str, Any] = {}
|
|
158
|
+
if getattr(args, "name", None):
|
|
159
|
+
payload["name"] = args.name
|
|
160
|
+
if getattr(args, "status", None):
|
|
161
|
+
payload["status"] = args.status
|
|
162
|
+
if getattr(args, "auto_find_email", None) is not None:
|
|
163
|
+
payload["autoFindEmail"] = args.auto_find_email
|
|
164
|
+
if getattr(args, "auto_find_phone", None) is not None:
|
|
165
|
+
payload["autoFindPhone"] = args.auto_find_phone
|
|
166
|
+
if getattr(args, "auto_broaden", None) is not None:
|
|
167
|
+
payload["autoBroadenSearch"] = args.auto_broaden
|
|
168
|
+
|
|
169
|
+
data = runtime.request_api(
|
|
170
|
+
"PATCH", f"/api/agents/{args.agent_id}",
|
|
171
|
+
base_url=args.base_url, token=token,
|
|
172
|
+
use_x_api_key=args.use_x_api_key,
|
|
173
|
+
json=payload,
|
|
174
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
175
|
+
)
|
|
176
|
+
runtime.print_json(data, args.compact)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def cmd_agents_delete(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
180
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
181
|
+
data = runtime.request_api(
|
|
182
|
+
"PATCH", f"/api/agents/{args.agent_id}",
|
|
183
|
+
base_url=args.base_url, token=token,
|
|
184
|
+
use_x_api_key=args.use_x_api_key,
|
|
185
|
+
json={"status": "DRAFT"},
|
|
186
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
187
|
+
)
|
|
188
|
+
sys.stderr.write(f"Agent {args.agent_id} deleted.\n")
|
|
189
|
+
runtime.print_json(data, args.compact)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
# Run management
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
def cmd_agents_run(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
197
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
198
|
+
data = runtime.request_api(
|
|
199
|
+
"POST", f"/api/agents/{args.agent_id}/run-now",
|
|
200
|
+
base_url=args.base_url, token=token,
|
|
201
|
+
use_x_api_key=args.use_x_api_key,
|
|
202
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
203
|
+
)
|
|
204
|
+
runtime.print_json(data, args.compact)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def cmd_agents_runs(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
208
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
209
|
+
params = {"limit": max(1, int(getattr(args, "limit", 10) or 10))}
|
|
210
|
+
data = runtime.request_api(
|
|
211
|
+
"GET", f"/api/agents/{args.agent_id}/runs",
|
|
212
|
+
base_url=args.base_url, token=token,
|
|
213
|
+
use_x_api_key=args.use_x_api_key,
|
|
214
|
+
params=params,
|
|
215
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
216
|
+
)
|
|
217
|
+
runtime.print_json(data, args.compact)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def cmd_agents_watch(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
221
|
+
"""Trigger a run and poll until complete."""
|
|
222
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
223
|
+
|
|
224
|
+
# Trigger
|
|
225
|
+
run_data = runtime.request_api(
|
|
226
|
+
"POST", f"/api/agents/{args.agent_id}/run-now",
|
|
227
|
+
base_url=args.base_url, token=token,
|
|
228
|
+
use_x_api_key=args.use_x_api_key,
|
|
229
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
230
|
+
)
|
|
231
|
+
run_id = run_data.get("id", "")
|
|
232
|
+
sys.stderr.write(f"Run {run_id} queued. Watching...\n")
|
|
233
|
+
|
|
234
|
+
interval = max(5, int(getattr(args, "interval", 10) or 10))
|
|
235
|
+
max_wait = max(60, int(getattr(args, "max_wait", 1800) or 1800))
|
|
236
|
+
elapsed = 0
|
|
237
|
+
|
|
238
|
+
while elapsed < max_wait:
|
|
239
|
+
time.sleep(interval)
|
|
240
|
+
elapsed += interval
|
|
241
|
+
|
|
242
|
+
runs_data = runtime.request_api(
|
|
243
|
+
"GET", f"/api/agents/{args.agent_id}/runs",
|
|
244
|
+
base_url=args.base_url, token=token,
|
|
245
|
+
use_x_api_key=args.use_x_api_key,
|
|
246
|
+
params={"limit": 1},
|
|
247
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
248
|
+
)
|
|
249
|
+
runs = runs_data.get("runs", [])
|
|
250
|
+
if not runs:
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
latest = runs[0]
|
|
254
|
+
status = latest.get("status", "")
|
|
255
|
+
leads = latest.get("resultCounts", {}).get("leads", 0)
|
|
256
|
+
sys.stderr.write(f" [{elapsed}s] {status} | leads={leads}\n")
|
|
257
|
+
|
|
258
|
+
if status in ("SUCCEEDED", "FAILED"):
|
|
259
|
+
runtime.print_json(latest, args.compact)
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
sys.stderr.write(f"Timed out after {max_wait}s. Run may still be in progress.\n")
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
# AI generation
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
def cmd_agents_generate_persona(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
270
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
271
|
+
payload: Dict[str, Any] = {"targetType": getattr(args, "target", "LEADS") or "LEADS"}
|
|
272
|
+
|
|
273
|
+
current = getattr(args, "current_config", None)
|
|
274
|
+
if current:
|
|
275
|
+
payload["currentPersonaConfig"] = runtime.load_json_input(current)
|
|
276
|
+
|
|
277
|
+
data = runtime.request_api(
|
|
278
|
+
"POST", "/api/agents/generate-persona",
|
|
279
|
+
base_url=args.base_url, token=token,
|
|
280
|
+
use_x_api_key=args.use_x_api_key,
|
|
281
|
+
json=payload,
|
|
282
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
283
|
+
)
|
|
284
|
+
runtime.print_json(data, args.compact)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def cmd_agents_generate_topics(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
288
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
289
|
+
payload: Dict[str, Any] = {"targetType": getattr(args, "target", "LEADS") or "LEADS"}
|
|
290
|
+
|
|
291
|
+
persona = getattr(args, "persona", None)
|
|
292
|
+
if persona:
|
|
293
|
+
payload["personaConfig"] = runtime.load_json_input(persona)
|
|
294
|
+
|
|
295
|
+
current = getattr(args, "current_topics", None)
|
|
296
|
+
if current:
|
|
297
|
+
payload["currentTopics"] = [t.strip() for t in current.split(",") if t.strip()]
|
|
298
|
+
|
|
299
|
+
data = runtime.request_api(
|
|
300
|
+
"POST", "/api/agents/generate-signal-topics",
|
|
301
|
+
base_url=args.base_url, token=token,
|
|
302
|
+
use_x_api_key=args.use_x_api_key,
|
|
303
|
+
json=payload,
|
|
304
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
305
|
+
)
|
|
306
|
+
runtime.print_json(data, args.compact)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def cmd_agents_generate_job_signal(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
310
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
311
|
+
payload: Dict[str, Any] = {"targetType": getattr(args, "target", "LEADS") or "LEADS"}
|
|
312
|
+
|
|
313
|
+
persona = getattr(args, "persona", None)
|
|
314
|
+
if persona:
|
|
315
|
+
payload["personaConfig"] = runtime.load_json_input(persona)
|
|
316
|
+
|
|
317
|
+
current_titles = getattr(args, "current_titles", None)
|
|
318
|
+
if current_titles:
|
|
319
|
+
payload["currentJobTitles"] = [t.strip() for t in current_titles.split(",") if t.strip()]
|
|
320
|
+
|
|
321
|
+
current_keywords = getattr(args, "current_keywords", None)
|
|
322
|
+
if current_keywords:
|
|
323
|
+
payload["currentKeywords"] = [k.strip() for k in current_keywords.split(",") if k.strip()]
|
|
324
|
+
|
|
325
|
+
data = runtime.request_api(
|
|
326
|
+
"POST", "/api/agents/generate-job-signal",
|
|
327
|
+
base_url=args.base_url, token=token,
|
|
328
|
+
use_x_api_key=args.use_x_api_key,
|
|
329
|
+
json=payload,
|
|
330
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
331
|
+
)
|
|
332
|
+
runtime.print_json(data, args.compact)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# ---------------------------------------------------------------------------
|
|
336
|
+
# Skill packs / signals
|
|
337
|
+
# ---------------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
def cmd_agents_signals(args: argparse.Namespace, *, runtime: AgentCommandRuntime) -> None:
|
|
340
|
+
token = runtime.resolve_token(args.token, required=True)
|
|
341
|
+
data = runtime.request_api(
|
|
342
|
+
"GET", "/api/agents/skill-packs",
|
|
343
|
+
base_url=args.base_url, token=token,
|
|
344
|
+
use_x_api_key=args.use_x_api_key,
|
|
345
|
+
timeout=args.timeout, verbose=args.verbose,
|
|
346
|
+
)
|
|
347
|
+
runtime.print_json(data, args.compact)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# ---------------------------------------------------------------------------
|
|
351
|
+
# Parser registration
|
|
352
|
+
# ---------------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
def register_agents_subcommands(
|
|
355
|
+
subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
|
|
356
|
+
*,
|
|
357
|
+
add_api_common_arguments: Callable[[argparse.ArgumentParser], None],
|
|
358
|
+
handlers: Dict[str, Callable[[argparse.Namespace], None]],
|
|
359
|
+
) -> None:
|
|
360
|
+
pa = subparsers.add_parser("agents", help="Scheduled agent management")
|
|
361
|
+
agents_sub = pa.add_subparsers(dest="agents_cmd", required=True)
|
|
362
|
+
|
|
363
|
+
# -- list --
|
|
364
|
+
pal = agents_sub.add_parser("list", help="List all agents")
|
|
365
|
+
add_api_common_arguments(pal)
|
|
366
|
+
pal.set_defaults(func=handlers["list"])
|
|
367
|
+
|
|
368
|
+
# -- get --
|
|
369
|
+
pag = agents_sub.add_parser("get", help="Get one agent by id")
|
|
370
|
+
pag.add_argument("agent_id", help="Agent ID")
|
|
371
|
+
add_api_common_arguments(pag)
|
|
372
|
+
pag.set_defaults(func=handlers["get"])
|
|
373
|
+
|
|
374
|
+
# -- create --
|
|
375
|
+
pac = agents_sub.add_parser("create", help="Create a new agent")
|
|
376
|
+
pac.add_argument("--name", help="Agent name")
|
|
377
|
+
pac.add_argument("--target", choices=["LEADS", "COMPANIES"], help="Target type")
|
|
378
|
+
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)")
|
|
380
|
+
pac.add_argument("--schedule-tz", dest="schedule_tz", help="Timezone (default: UTC)")
|
|
381
|
+
pac.add_argument("--auto-find-email", dest="auto_find_email", action="store_true", default=False, help="Enable auto email finder")
|
|
382
|
+
pac.add_argument("--auto-find-phone", dest="auto_find_phone", action="store_true", default=False, help="Enable auto phone finder")
|
|
383
|
+
pac.add_argument("--auto-broaden", dest="auto_broaden", action="store_true", default=False, help="Enable auto broaden search")
|
|
384
|
+
pac.add_argument("--status", choices=["DRAFT", "ACTIVE", "PAUSED"], help="Initial status")
|
|
385
|
+
pac.add_argument("--from-file", dest="from_file", help="Path to JSON payload file")
|
|
386
|
+
pac.add_argument("--generate", action="store_true", help="Auto-generate persona, topics, and job signal config")
|
|
387
|
+
add_api_common_arguments(pac)
|
|
388
|
+
pac.set_defaults(func=handlers["create"])
|
|
389
|
+
|
|
390
|
+
# -- update --
|
|
391
|
+
pau = agents_sub.add_parser("update", help="Update an existing agent")
|
|
392
|
+
pau.add_argument("agent_id", help="Agent ID")
|
|
393
|
+
pau.add_argument("--name", help="New agent name")
|
|
394
|
+
pau.add_argument("--status", choices=["DRAFT", "ACTIVE", "PAUSED"], help="New status")
|
|
395
|
+
pau.add_argument("--auto-find-email", dest="auto_find_email", type=_bool_arg, default=None, help="Enable/disable auto email finder")
|
|
396
|
+
pau.add_argument("--auto-find-phone", dest="auto_find_phone", type=_bool_arg, default=None, help="Enable/disable auto phone finder")
|
|
397
|
+
pau.add_argument("--auto-broaden", dest="auto_broaden", type=_bool_arg, default=None, help="Enable/disable auto broaden search")
|
|
398
|
+
pau.add_argument("--from-file", dest="from_file", help="Path to JSON patch payload file")
|
|
399
|
+
add_api_common_arguments(pau)
|
|
400
|
+
pau.set_defaults(func=handlers["update"])
|
|
401
|
+
|
|
402
|
+
# -- delete --
|
|
403
|
+
pad = agents_sub.add_parser("delete", help="Delete an agent (sets status to DRAFT)")
|
|
404
|
+
pad.add_argument("agent_id", help="Agent ID")
|
|
405
|
+
add_api_common_arguments(pad)
|
|
406
|
+
pad.set_defaults(func=handlers["delete"])
|
|
407
|
+
|
|
408
|
+
# -- run --
|
|
409
|
+
par = agents_sub.add_parser("run", help="Trigger an immediate run")
|
|
410
|
+
par.add_argument("agent_id", help="Agent ID")
|
|
411
|
+
add_api_common_arguments(par)
|
|
412
|
+
par.set_defaults(func=handlers["run"])
|
|
413
|
+
|
|
414
|
+
# -- runs --
|
|
415
|
+
pars = agents_sub.add_parser("runs", help="List recent runs for an agent")
|
|
416
|
+
pars.add_argument("agent_id", help="Agent ID")
|
|
417
|
+
pars.add_argument("--limit", type=int, default=10, help="Max runs to return (default: 10)")
|
|
418
|
+
add_api_common_arguments(pars)
|
|
419
|
+
pars.set_defaults(func=handlers["runs"])
|
|
420
|
+
|
|
421
|
+
# -- watch --
|
|
422
|
+
paw = agents_sub.add_parser("watch", help="Trigger a run and poll until complete")
|
|
423
|
+
paw.add_argument("agent_id", help="Agent ID")
|
|
424
|
+
paw.add_argument("--interval", type=int, default=10, help="Poll interval in seconds (default: 10)")
|
|
425
|
+
paw.add_argument("--max-wait", dest="max_wait", type=int, default=1800, help="Max wait in seconds (default: 1800)")
|
|
426
|
+
add_api_common_arguments(paw)
|
|
427
|
+
paw.set_defaults(func=handlers["watch"])
|
|
428
|
+
|
|
429
|
+
# -- generate-persona --
|
|
430
|
+
pagp = agents_sub.add_parser("generate-persona", help="Generate ICP persona config via AI")
|
|
431
|
+
pagp.add_argument("--target", choices=["LEADS", "COMPANIES"], default="LEADS", help="Target type")
|
|
432
|
+
pagp.add_argument("--current-config", dest="current_config", help="Path to current persona config JSON")
|
|
433
|
+
add_api_common_arguments(pagp)
|
|
434
|
+
pagp.set_defaults(func=handlers["generate_persona"])
|
|
435
|
+
|
|
436
|
+
# -- generate-topics --
|
|
437
|
+
pagt = agents_sub.add_parser("generate-topics", help="Generate signal topic suggestions via AI")
|
|
438
|
+
pagt.add_argument("--target", choices=["LEADS", "COMPANIES"], default="LEADS", help="Target type")
|
|
439
|
+
pagt.add_argument("--persona", help="Path to persona config JSON")
|
|
440
|
+
pagt.add_argument("--current-topics", dest="current_topics", help="Comma-separated current topics")
|
|
441
|
+
add_api_common_arguments(pagt)
|
|
442
|
+
pagt.set_defaults(func=handlers["generate_topics"])
|
|
443
|
+
|
|
444
|
+
# -- generate-job-signal --
|
|
445
|
+
pagj = agents_sub.add_parser("generate-job-signal", help="Generate job signal config via AI")
|
|
446
|
+
pagj.add_argument("--target", choices=["LEADS", "COMPANIES"], default="LEADS", help="Target type")
|
|
447
|
+
pagj.add_argument("--persona", help="Path to persona config JSON")
|
|
448
|
+
pagj.add_argument("--current-titles", dest="current_titles", help="Comma-separated current job titles")
|
|
449
|
+
pagj.add_argument("--current-keywords", dest="current_keywords", help="Comma-separated current keywords")
|
|
450
|
+
add_api_common_arguments(pagj)
|
|
451
|
+
pagj.set_defaults(func=handlers["generate_job_signal"])
|
|
452
|
+
|
|
453
|
+
# -- signals --
|
|
454
|
+
pass_ = agents_sub.add_parser("signals", help="List available signal skill packs")
|
|
455
|
+
add_api_common_arguments(pass_)
|
|
456
|
+
pass_.set_defaults(func=handlers["signals"])
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _bool_arg(v: str) -> bool:
|
|
460
|
+
"""Parse boolean CLI argument values."""
|
|
461
|
+
if v.lower() in ("true", "1", "yes"):
|
|
462
|
+
return True
|
|
463
|
+
if v.lower() in ("false", "0", "no"):
|
|
464
|
+
return False
|
|
465
|
+
raise argparse.ArgumentTypeError(f"Expected true/false, got: {v}")
|
|
@@ -379,7 +379,22 @@ def register_columns_subcommands(
|
|
|
379
379
|
add_api_common_arguments(pcl)
|
|
380
380
|
pcl.set_defaults(func=handlers["list"])
|
|
381
381
|
|
|
382
|
-
pcc = col_sub.add_parser(
|
|
382
|
+
pcc = col_sub.add_parser(
|
|
383
|
+
"create",
|
|
384
|
+
help="Create a column",
|
|
385
|
+
description=(
|
|
386
|
+
"Create a column from a ColumnCreate payload.\n"
|
|
387
|
+
"For llm_enrichment, there are two valid authoring paths:\n"
|
|
388
|
+
"- generated path: send config.instructions and let the API compile config.advancedPrompt\n"
|
|
389
|
+
"- manual path: send config.advancedPrompt directly when you want exact prompt control"
|
|
390
|
+
),
|
|
391
|
+
epilog=(
|
|
392
|
+
"Recommended flow for llm_enrichment:\n\n"
|
|
393
|
+
"autotouch columns recipe --type llm_enrichment --output human\n\n"
|
|
394
|
+
"The runnable prompt is always config.advancedPrompt before execution."
|
|
395
|
+
),
|
|
396
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
397
|
+
)
|
|
383
398
|
pcc.add_argument("--table-id", required=True)
|
|
384
399
|
pcc.add_argument("--data-json", help="ColumnCreate payload JSON")
|
|
385
400
|
pcc.add_argument("--data-file", help="ColumnCreate payload file path")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Autotouch CLI Reference
|
|
2
2
|
|
|
3
|
-
Generated from the installed parser for `autotouch-cli` `0.2.
|
|
3
|
+
Generated from the installed parser for `autotouch-cli` `0.2.61`.
|
|
4
4
|
Manifest schema version: `2`.
|
|
5
5
|
|
|
6
6
|
## Output Modes
|
|
@@ -653,6 +653,11 @@ Column operations
|
|
|
653
653
|
|
|
654
654
|
Create a column
|
|
655
655
|
|
|
656
|
+
Create a column from a ColumnCreate payload.
|
|
657
|
+
For llm_enrichment, there are two valid authoring paths:
|
|
658
|
+
- generated path: send config.instructions and let the API compile config.advancedPrompt
|
|
659
|
+
- manual path: send config.advancedPrompt directly when you want exact prompt control
|
|
660
|
+
|
|
656
661
|
- Auth: `developer_key_or_user_session`
|
|
657
662
|
- Stability: `stable`
|
|
658
663
|
- Destructive: `no`
|
|
@@ -669,6 +674,15 @@ Create a column
|
|
|
669
674
|
[--output {json,ndjson,human}] [--compact]
|
|
670
675
|
[--select SELECT |
|
|
671
676
|
--json-pointer JSON_POINTER] [--verbose]`
|
|
677
|
+
- Notes:
|
|
678
|
+
|
|
679
|
+
```text
|
|
680
|
+
Recommended flow for llm_enrichment:
|
|
681
|
+
|
|
682
|
+
autotouch columns recipe --type llm_enrichment --output human
|
|
683
|
+
|
|
684
|
+
The runnable prompt is always config.advancedPrompt before execution.
|
|
685
|
+
```
|
|
672
686
|
- Options:
|
|
673
687
|
- `--table-id` (required; kind=string)
|
|
674
688
|
- `--data-json` (kind=json; input=json): ColumnCreate payload JSON
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.2.
|
|
2
|
+
"version": "0.2.61",
|
|
3
3
|
"manifest_schema_version": 2,
|
|
4
4
|
"entry_points": {
|
|
5
5
|
"autotouch": "autotouch_cli.cli:main",
|
|
@@ -14777,8 +14777,8 @@
|
|
|
14777
14777
|
"aliases": [],
|
|
14778
14778
|
"group": "columns",
|
|
14779
14779
|
"help": "Create a column",
|
|
14780
|
-
"description":
|
|
14781
|
-
"notes":
|
|
14780
|
+
"description": "Create a column from a ColumnCreate payload.\nFor llm_enrichment, there are two valid authoring paths:\n- generated path: send config.instructions and let the API compile config.advancedPrompt\n- manual path: send config.advancedPrompt directly when you want exact prompt control",
|
|
14781
|
+
"notes": "Recommended flow for llm_enrichment:\n\nautotouch columns recipe --type llm_enrichment --output human\n\nThe runnable prompt is always config.advancedPrompt before execution.",
|
|
14782
14782
|
"required_flags": [
|
|
14783
14783
|
"--table-id"
|
|
14784
14784
|
],
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
import argparse
|
|
10
10
|
from typing import Any, Callable, Dict, List, Optional
|
|
11
11
|
|
|
12
|
+
from autotouch_cli.commands.agents import register_agents_subcommands
|
|
12
13
|
from autotouch_cli.commands.auth import register_auth_subcommands
|
|
13
14
|
from autotouch_cli.commands.cells import register_cells_subcommands
|
|
14
15
|
from autotouch_cli.commands.columns import register_columns_subcommands
|
|
@@ -756,6 +757,26 @@ def build_parser(
|
|
|
756
757
|
recipe_types=LINKEDIN_RECIPE_TYPES,
|
|
757
758
|
)
|
|
758
759
|
|
|
760
|
+
# agents
|
|
761
|
+
register_agents_subcommands(
|
|
762
|
+
sub,
|
|
763
|
+
add_api_common_arguments=add_api,
|
|
764
|
+
handlers={
|
|
765
|
+
"list": commands["agents_list"],
|
|
766
|
+
"get": commands["agents_get"],
|
|
767
|
+
"create": commands["agents_create"],
|
|
768
|
+
"update": commands["agents_update"],
|
|
769
|
+
"delete": commands["agents_delete"],
|
|
770
|
+
"run": commands["agents_run"],
|
|
771
|
+
"runs": commands["agents_runs"],
|
|
772
|
+
"watch": commands["agents_watch"],
|
|
773
|
+
"generate_persona": commands["agents_generate_persona"],
|
|
774
|
+
"generate_topics": commands["agents_generate_topics"],
|
|
775
|
+
"generate_job_signal": commands["agents_generate_job_signal"],
|
|
776
|
+
"signals": commands["agents_signals"],
|
|
777
|
+
},
|
|
778
|
+
)
|
|
779
|
+
|
|
759
780
|
# Backward-compatible aliases
|
|
760
781
|
# run
|
|
761
782
|
palias_run = sub.add_parser("run", help="Alias for: columns run")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: autotouch-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.63
|
|
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
|
|
@@ -158,6 +158,11 @@ For `llm_enrichment` in `agent` mode, the recommended path is:
|
|
|
158
158
|
|
|
159
159
|
Only send `user_schema` / `response_schema` when you intentionally want to override the generated schema and keep it aligned yourself. The installed recipe surface at `autotouch columns recipe --type llm_enrichment` follows this contract.
|
|
160
160
|
|
|
161
|
+
Schema ownership rules:
|
|
162
|
+
- Accepted generated schemas and explicit user schemas are the saved output contract.
|
|
163
|
+
- Row execution must not add fields, rename fields, or replace a valid locked schema.
|
|
164
|
+
- Agent-mode evidence/scored state decides which values may fill the schema; the finalizer formats those values and schema validation gates persistence.
|
|
165
|
+
|
|
161
166
|
Prompt variables in authored prompts support nested JSON access:
|
|
162
167
|
- Use flat row variables like `{{company_name}}` for scalar columns.
|
|
163
168
|
- Use dotted placeholders like `{{linkedin_lookup.linkedin_url}}` when the source column stores JSON or stringified JSON.
|
|
@@ -17,6 +17,7 @@ autotouch_cli.egg-info/entry_points.txt
|
|
|
17
17
|
autotouch_cli.egg-info/requires.txt
|
|
18
18
|
autotouch_cli.egg-info/top_level.txt
|
|
19
19
|
autotouch_cli/commands/__init__.py
|
|
20
|
+
autotouch_cli/commands/agents.py
|
|
20
21
|
autotouch_cli/commands/auth.py
|
|
21
22
|
autotouch_cli/commands/cells.py
|
|
22
23
|
autotouch_cli/commands/columns.py
|
|
@@ -158,6 +158,14 @@ def _mapping_text(value: Any) -> str:
|
|
|
158
158
|
return _text(value)
|
|
159
159
|
|
|
160
160
|
|
|
161
|
+
def _llm_advanced_prompt_text(config: Dict[str, Any]) -> str:
|
|
162
|
+
return (
|
|
163
|
+
_text(config.get("advancedPrompt"))
|
|
164
|
+
or _text(config.get("advanced_prompt"))
|
|
165
|
+
or _text(config.get("prompt"))
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
161
169
|
def _llm_provider_values() -> Tuple[str, ...]:
|
|
162
170
|
return ("llm", "gemini", "xai", "openai", "anthropic", "groq")
|
|
163
171
|
|
|
@@ -211,7 +219,7 @@ _FORMATTER_POLICY = AutoRunExecutionPolicy(
|
|
|
211
219
|
|
|
212
220
|
_LLM_POLICY = AutoRunExecutionPolicy(
|
|
213
221
|
name="llm",
|
|
214
|
-
orchestrator_provider="
|
|
222
|
+
orchestrator_provider="llm",
|
|
215
223
|
paid=True,
|
|
216
224
|
force_rerun=False,
|
|
217
225
|
respect_existing_done=True,
|
|
@@ -378,13 +386,14 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
|
|
|
378
386
|
},
|
|
379
387
|
},
|
|
380
388
|
recipe_notes=(
|
|
381
|
-
"
|
|
389
|
+
"The runnable prompt is always config.advancedPrompt before execution.",
|
|
390
|
+
"Generated path: provide instructions and let the API compile config.advancedPrompt for you.",
|
|
391
|
+
"Manual path: write config.advancedPrompt directly when you want exact control over the final prompt.",
|
|
382
392
|
"When the platform generates the runnable prompt from instructions, keep useAutoSchema=true unless you intentionally want to own the schema yourself.",
|
|
383
393
|
"Do not send user_schema/response_schema in that default generated-prompt path; prompt/schema drift is possible if you later change the generated prompt but keep a frozen custom schema.",
|
|
384
394
|
"Basic is for extraction, scoring, or classification using fields already in the row. Agent is for tasks that may need outside lookup or web research.",
|
|
385
395
|
"Create/update always materializes the runnable prompt into config.advancedPrompt before execution.",
|
|
386
396
|
"Generated agent-mode prompts always include company/requester context.",
|
|
387
|
-
"Basic mode stays manual-prompt-first; write the prompt directly from the user goal/preferences.",
|
|
388
397
|
"Runtime only injects values for placeholders the prompt explicitly references.",
|
|
389
398
|
"Manual/basic prompts should use explicit placeholders like {{company_name}}, {{domain}}, or {{user_value_proposition}} when those values are needed.",
|
|
390
399
|
"Agent mode is JSON-oriented.",
|
|
@@ -786,10 +795,10 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
|
|
|
786
795
|
},
|
|
787
796
|
},
|
|
788
797
|
recipe_notes=(
|
|
789
|
-
"
|
|
798
|
+
"Strict mode requires `companyDomain`; LinkedIn is optional.",
|
|
790
799
|
"Set `config.requireCompanyDomain=false` only for single-mode LinkedIn-first syncs. In that relaxed mode, `linkedinUrl` becomes required and multi/array mode stays unsupported.",
|
|
791
|
-
"
|
|
792
|
-
"If companyDomain is missing in the table, derive or enrich that domain column first.",
|
|
800
|
+
"Strict single-mode sync can use either a usable hard-identity mapping (LinkedIn, email, or phone) or mapped `firstName` / `lastName` values that create a provisional lead.",
|
|
801
|
+
"If `companyDomain` is missing in the table, derive or enrich that domain column first, or explicitly opt into `config.requireCompanyDomain=false` for LinkedIn-first sync.",
|
|
793
802
|
"Primitive source columns map directly. If a mapped source column stores structured JSON, send an object with column + path, for example {\"column\": \"work_email\", \"path\": \"response\"} or {\"column\": \"mobile_phone\", \"path\": \"mobile_number\"}.",
|
|
794
803
|
"For firstName and lastName, prefer authoritative structured source fields when available, for example {\"column\": \"linkedin_profile_lookup_raw\", \"path\": \"first_name\"} and {\"column\": \"linkedin_profile_lookup_raw\", \"path\": \"last_name\"}. Avoid mapping a combined full-name column directly unless that is an intentional fallback and you accept imperfect CRM data.",
|
|
795
804
|
"Sync to Leads is non-billable.",
|
|
@@ -803,7 +812,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
|
|
|
803
812
|
"requirements": {
|
|
804
813
|
"config_provider": "add_to_crm",
|
|
805
814
|
"field_mappings": "required",
|
|
806
|
-
"eligibility": "company domain plus
|
|
815
|
+
"eligibility": "strict mode requires company domain plus either a usable hard identity signal (LinkedIn, email, or phone) or, in single mode, mapped firstName/lastName; LinkedIn-first mode requires linkedinUrl when companyDomain is optional",
|
|
807
816
|
},
|
|
808
817
|
"billing": {
|
|
809
818
|
"billable": False,
|
|
@@ -815,7 +824,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
|
|
|
815
824
|
"primary_provider_value": "add_to_crm",
|
|
816
825
|
"field_mappings_field": "config.fieldMappings",
|
|
817
826
|
"source_columns_field": "config.sourceColumns",
|
|
818
|
-
"eligibility": "company domain plus
|
|
827
|
+
"eligibility": "strict mode requires company domain plus either a usable hard identity signal (LinkedIn, email, or phone) or, in single mode, mapped firstName/lastName; LinkedIn-first mode requires linkedinUrl when companyDomain is optional",
|
|
819
828
|
"replay_behavior": "source updates only; explicit run required to backfill older upstream results",
|
|
820
829
|
"structured_source_mapping": {
|
|
821
830
|
"rule": "primitive columns map directly; structured columns use {column, path}",
|
|
@@ -830,7 +839,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
|
|
|
830
839
|
composition_contract={
|
|
831
840
|
"consumes": {
|
|
832
841
|
"kind": "crm_export_inputs",
|
|
833
|
-
"shape": "mapped source row values including company domain plus
|
|
842
|
+
"shape": "mapped source row values including company domain plus hard identity or, in single mode, provisional firstName/lastName fallback; structured source columns use explicit {column, path} mappings to identify the exact field",
|
|
834
843
|
},
|
|
835
844
|
"produces": {
|
|
836
845
|
"kind": "lead_ids",
|
|
@@ -1357,6 +1366,38 @@ def validate_column_provider_contract(
|
|
|
1357
1366
|
)
|
|
1358
1367
|
return contract
|
|
1359
1368
|
|
|
1369
|
+
if contract.key == "llm":
|
|
1370
|
+
prompt_source = _normalize(cfg.get("promptSource") or cfg.get("prompt_source"))
|
|
1371
|
+
instructions = _text(cfg.get("instructions"))
|
|
1372
|
+
advanced_prompt = _llm_advanced_prompt_text(cfg)
|
|
1373
|
+
|
|
1374
|
+
if prompt_source == "generated":
|
|
1375
|
+
if not instructions:
|
|
1376
|
+
raise ProviderContractValidationError(
|
|
1377
|
+
"missing_required_field",
|
|
1378
|
+
"Generated LLM prompt mode requires config.instructions.",
|
|
1379
|
+
hint="Use instructions for shorthand/high-level intent, or switch to config.promptSource='manual' and write config.advancedPrompt directly.",
|
|
1380
|
+
)
|
|
1381
|
+
return contract
|
|
1382
|
+
|
|
1383
|
+
if prompt_source == "manual":
|
|
1384
|
+
if not advanced_prompt:
|
|
1385
|
+
raise ProviderContractValidationError(
|
|
1386
|
+
"missing_required_field",
|
|
1387
|
+
"Manual LLM prompt mode requires config.advancedPrompt.",
|
|
1388
|
+
hint="Write config.advancedPrompt directly, or remove config.promptSource and supply config.instructions for the generated path.",
|
|
1389
|
+
)
|
|
1390
|
+
return contract
|
|
1391
|
+
|
|
1392
|
+
if instructions or advanced_prompt:
|
|
1393
|
+
return contract
|
|
1394
|
+
|
|
1395
|
+
raise ProviderContractValidationError(
|
|
1396
|
+
"missing_required_field",
|
|
1397
|
+
"LLM enrichment columns require either config.instructions or config.advancedPrompt.",
|
|
1398
|
+
hint="Use instructions for the generated path or write config.advancedPrompt directly for the manual path.",
|
|
1399
|
+
)
|
|
1400
|
+
|
|
1360
1401
|
if contract.key == "add_to_sequence":
|
|
1361
1402
|
if not _text(cfg.get("sequenceId")):
|
|
1362
1403
|
raise ProviderContractValidationError(
|
|
@@ -1549,7 +1590,7 @@ def orchestrated_runtime_provider_keys(*, include_formatter: bool = False) -> Tu
|
|
|
1549
1590
|
providers.append(policy.orchestrator_provider)
|
|
1550
1591
|
continue
|
|
1551
1592
|
if contract.key == "llm":
|
|
1552
|
-
providers.extend(["gemini", "xai"])
|
|
1593
|
+
providers.extend(["llm", "gemini", "xai"])
|
|
1553
1594
|
continue
|
|
1554
1595
|
providers.append(policy.orchestrator_provider)
|
|
1555
1596
|
return tuple(dict.fromkeys(provider for provider in providers if provider))
|
|
@@ -1586,9 +1627,9 @@ def resolve_runtime_orchestrator_provider(
|
|
|
1586
1627
|
return None
|
|
1587
1628
|
if contract.key == "llm":
|
|
1588
1629
|
runtime_provider = _normalize((config or {}).get("provider"))
|
|
1589
|
-
if runtime_provider in {"gemini", "xai"}:
|
|
1630
|
+
if runtime_provider in {"llm", "gemini", "xai"}:
|
|
1590
1631
|
return runtime_provider
|
|
1591
|
-
if runtime_provider
|
|
1632
|
+
if not runtime_provider:
|
|
1592
1633
|
return contract.execution_policy.orchestrator_provider
|
|
1593
1634
|
return None
|
|
1594
1635
|
return contract.execution_policy.orchestrator_provider
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|