autotouch-cli 0.2.61__tar.gz → 0.2.64__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.61 → autotouch_cli-0.2.64}/PKG-INFO +6 -1
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/README.md +5 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/cli.py +40 -0
- autotouch_cli-0.2.64/autotouch_cli/commands/agents.py +465 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/data/cli-manifest.json +2538 -1
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/parser.py +21 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli.egg-info/PKG-INFO +6 -1
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli.egg-info/SOURCES.txt +1 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_shared/provider_registry.py +10 -10
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/pyproject.toml +1 -1
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/MANIFEST.in +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/__init__.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/cli_contracts.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/__init__.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/auth.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/cells.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/columns.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/jobs.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/leads.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/linkedin.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/prompts.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/rows.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/search.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/sequences.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/tables.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/tasks.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/webhooks.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/commands/workspace_secrets.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/core/__init__.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/core/auth.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/core/config.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/core/csv_import.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/core/http.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/core/io.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/core/output.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/core/polling.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/core/run.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/core/validation.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/data/CLI_REFERENCE.md +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/exceptions.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/mongo_status.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/parser_groups.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/sequence_support.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli/templates.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli.egg-info/dependency_links.txt +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli.egg-info/entry_points.txt +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli.egg-info/requires.txt +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_cli.egg-info/top_level.txt +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_shared/__init__.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_shared/linkedin_contract.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/autotouch_shared/search_contract.py +0 -0
- {autotouch_cli-0.2.61 → autotouch_cli-0.2.64}/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.64
|
|
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}")
|