guzli-cli 0.2.0__py3-none-any.whl
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.
- guzli/__init__.py +3 -0
- guzli/__main__.py +4 -0
- guzli/auth.py +182 -0
- guzli/cli.py +84 -0
- guzli/commands/__init__.py +1 -0
- guzli/commands/admin.py +421 -0
- guzli/commands/auth.py +502 -0
- guzli/commands/campaign_launch.py +188 -0
- guzli/commands/campaigns.py +571 -0
- guzli/commands/common.py +42 -0
- guzli/commands/conversations.py +404 -0
- guzli/commands/integrations.py +48 -0
- guzli/commands/telephony.py +274 -0
- guzli/commands/voice_calls.py +270 -0
- guzli/config.py +137 -0
- guzli/config_store.py +218 -0
- guzli/errors.py +47 -0
- guzli/http_client.py +213 -0
- guzli/mcp/__init__.py +1 -0
- guzli/mcp/server.py +320 -0
- guzli/models.py +173 -0
- guzli/oauth_callback.py +126 -0
- guzli/output.py +79 -0
- guzli/runtime.py +159 -0
- guzli/services/__init__.py +29 -0
- guzli/services/api_keys.py +105 -0
- guzli/services/calls.py +48 -0
- guzli/services/campaigns.py +193 -0
- guzli/services/cli_auth.py +103 -0
- guzli/services/conversations.py +112 -0
- guzli/services/integrations.py +18 -0
- guzli/services/replies.py +21 -0
- guzli/services/telephony.py +80 -0
- guzli/services/voice_profiles.py +56 -0
- guzli/services/webhooks.py +169 -0
- guzli/types.py +15 -0
- guzli/validators.py +83 -0
- guzli/workflows/__init__.py +13 -0
- guzli/workflows/campaign_launch.py +501 -0
- guzli_cli-0.2.0.data/data/share/guzli/CHANGELOG.md +16 -0
- guzli_cli-0.2.0.data/data/share/guzli/PROVENANCE.md +23 -0
- guzli_cli-0.2.0.data/data/share/guzli/SECURITY.md +18 -0
- guzli_cli-0.2.0.data/data/share/guzli/SECURITY_AUDIT.md +47 -0
- guzli_cli-0.2.0.data/data/share/guzli/SKILL.md +101 -0
- guzli_cli-0.2.0.data/data/share/guzli/examples/compliance-advisory-us.json +15 -0
- guzli_cli-0.2.0.data/data/share/guzli/examples/pizza-order-extraction.json +65 -0
- guzli_cli-0.2.0.data/data/share/guzli/references/api.md +245 -0
- guzli_cli-0.2.0.dist-info/METADATA +341 -0
- guzli_cli-0.2.0.dist-info/RECORD +53 -0
- guzli_cli-0.2.0.dist-info/WHEEL +5 -0
- guzli_cli-0.2.0.dist-info/entry_points.txt +3 -0
- guzli_cli-0.2.0.dist-info/licenses/LICENSE +201 -0
- guzli_cli-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Dict, List, Mapping
|
|
5
|
+
|
|
6
|
+
from guzli.commands.common import add_json_flag as _add_json_flag
|
|
7
|
+
from guzli.commands.common import parse_bool_flag as _parse_bool_flag
|
|
8
|
+
from guzli.errors import ApiError, ValidationError
|
|
9
|
+
from guzli.models import AgentReplyResult, ConversationDetailView, ConversationSummaryView
|
|
10
|
+
from guzli.runtime import RuntimeContext
|
|
11
|
+
from guzli.types import JsonValue
|
|
12
|
+
from guzli.validators import normalize_list, parse_iso_datetime, parse_json, require_non_empty
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def handle_conversations_list(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
16
|
+
params = build_conversation_list_params(args)
|
|
17
|
+
payload = context.conversations.list_conversations(params)
|
|
18
|
+
if args.json:
|
|
19
|
+
context.renderer.print_json(payload)
|
|
20
|
+
return
|
|
21
|
+
items = _extract_items(payload)
|
|
22
|
+
summaries = [ConversationSummaryView.from_json(item) for item in items]
|
|
23
|
+
context.renderer.print_conversation_list(summaries)
|
|
24
|
+
_print_cursor(payload)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def handle_conversation_get(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
28
|
+
conversation_id = require_non_empty(args.conversation_id, "conversation_id")
|
|
29
|
+
payload = context.conversations.get_conversation(conversation_id, not args.no_transcript)
|
|
30
|
+
if args.json:
|
|
31
|
+
context.renderer.print_json(payload)
|
|
32
|
+
return
|
|
33
|
+
if not isinstance(payload, Mapping):
|
|
34
|
+
raise ApiError("Unexpected response payload")
|
|
35
|
+
detail = ConversationDetailView.from_json(payload)
|
|
36
|
+
context.renderer.print_conversation_detail(detail, include_transcript=not args.no_transcript)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def handle_conversation_history(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
40
|
+
fingerprint_id = require_non_empty(args.fingerprint_id, "fingerprint_id")
|
|
41
|
+
payload = context.conversations.get_history(fingerprint_id)
|
|
42
|
+
context.renderer.print_json(payload)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def handle_conversation_filters(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
46
|
+
payload = context.conversations.get_filters()
|
|
47
|
+
context.renderer.print_json(payload)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def handle_conversation_interactions(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
51
|
+
conversation_id = require_non_empty(args.conversation_id, "conversation_id")
|
|
52
|
+
payload = context.conversations.list_automation_interactions(conversation_id)
|
|
53
|
+
context.renderer.print_json(payload)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def handle_conversation_assign(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
57
|
+
conversation_id = require_non_empty(args.conversation_id, "conversation_id")
|
|
58
|
+
assignment_type = require_non_empty(args.assignment_type, "assignment_type")
|
|
59
|
+
assigned_to_id = args.assigned_to_id
|
|
60
|
+
if assignment_type in {"agent", "team"} and not assigned_to_id:
|
|
61
|
+
raise ValidationError("assigned_to_id is required for agent/team assignments")
|
|
62
|
+
payload = {
|
|
63
|
+
"assignment_type": assignment_type,
|
|
64
|
+
"assigned_to_id": assigned_to_id,
|
|
65
|
+
"queue_label": args.queue_label,
|
|
66
|
+
"reason": args.reason,
|
|
67
|
+
}
|
|
68
|
+
response = context.conversations.update_assignment(conversation_id, payload)
|
|
69
|
+
context.renderer.print_json(response)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def handle_conversation_mark(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
73
|
+
conversation_id = require_non_empty(args.conversation_id, "conversation_id")
|
|
74
|
+
response = context.conversations.mark_conversation(conversation_id)
|
|
75
|
+
context.renderer.print_json(response)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def handle_conversation_unmark(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
79
|
+
conversation_id = require_non_empty(args.conversation_id, "conversation_id")
|
|
80
|
+
response = context.conversations.unmark_conversation(conversation_id)
|
|
81
|
+
context.renderer.print_json(response)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def handle_conversation_mute(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
85
|
+
conversation_id = require_non_empty(args.conversation_id, "conversation_id")
|
|
86
|
+
mute_expires_at = parse_iso_datetime(args.mute_expires_at, "mute_expires_at")
|
|
87
|
+
payload = {"mute_expires_at": mute_expires_at} if mute_expires_at else None
|
|
88
|
+
response = context.conversations.mute_conversation(conversation_id, payload)
|
|
89
|
+
context.renderer.print_json(response)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def handle_conversation_unmute(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
93
|
+
conversation_id = require_non_empty(args.conversation_id, "conversation_id")
|
|
94
|
+
response = context.conversations.unmute_conversation(conversation_id)
|
|
95
|
+
context.renderer.print_json(response)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def handle_conversation_close(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
99
|
+
conversation_id = require_non_empty(args.conversation_id, "conversation_id")
|
|
100
|
+
summary = _resolve_summary(args.summary, args.summary_json)
|
|
101
|
+
metadata_updates = parse_json(args.metadata_updates, "metadata_updates")
|
|
102
|
+
payload = {
|
|
103
|
+
"summary": summary,
|
|
104
|
+
"duration_seconds": args.duration_seconds,
|
|
105
|
+
"metadata_updates": metadata_updates or {},
|
|
106
|
+
}
|
|
107
|
+
response = context.conversations.close_conversation(conversation_id, payload)
|
|
108
|
+
context.renderer.print_json(response)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def handle_conversation_interaction_state(
|
|
112
|
+
context: RuntimeContext, args: argparse.Namespace
|
|
113
|
+
) -> None:
|
|
114
|
+
conversation_id = require_non_empty(args.conversation_id, "conversation_id")
|
|
115
|
+
state = require_non_empty(args.state, "state")
|
|
116
|
+
_validate_interaction_state(state)
|
|
117
|
+
payload = {
|
|
118
|
+
"state": state,
|
|
119
|
+
"interaction_id": args.interaction_id,
|
|
120
|
+
"reason": args.reason,
|
|
121
|
+
}
|
|
122
|
+
response = context.conversations.update_interaction_state(conversation_id, payload)
|
|
123
|
+
context.renderer.print_json(response)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def handle_conversation_metrics(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
127
|
+
metrics = normalize_list(args.metric)
|
|
128
|
+
params: Dict[str, object] = {"hours": args.hours}
|
|
129
|
+
if metrics:
|
|
130
|
+
params["metric"] = metrics
|
|
131
|
+
if args.metric_agent_id:
|
|
132
|
+
params["agent_id"] = args.metric_agent_id
|
|
133
|
+
if args.metric_channel:
|
|
134
|
+
params["channel"] = args.metric_channel
|
|
135
|
+
payload = context.conversations.get_metrics(params)
|
|
136
|
+
context.renderer.print_json(payload)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def handle_agent_reply(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
140
|
+
conversation_id = require_non_empty(args.conversation_id, "conversation_id")
|
|
141
|
+
message = require_non_empty(args.message, "message")
|
|
142
|
+
if len(message) > 4000:
|
|
143
|
+
raise ValidationError("message exceeds 4000 characters")
|
|
144
|
+
metadata = parse_json(args.metadata, "metadata") or {}
|
|
145
|
+
payload = {"message": message, "metadata": metadata}
|
|
146
|
+
response = context.replies.send_agent_reply(conversation_id, payload)
|
|
147
|
+
if args.json:
|
|
148
|
+
context.renderer.print_json(response)
|
|
149
|
+
return
|
|
150
|
+
if not isinstance(response, Mapping):
|
|
151
|
+
raise ApiError("Unexpected response payload")
|
|
152
|
+
result = AgentReplyResult.from_json(response)
|
|
153
|
+
context.renderer.print_reply_result(result)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def build_conversation_list_params(args: argparse.Namespace) -> Dict[str, object]:
|
|
157
|
+
params: Dict[str, object] = {}
|
|
158
|
+
status = normalize_list(args.status)
|
|
159
|
+
channel = normalize_list(args.channel)
|
|
160
|
+
tags = normalize_list(args.tags)
|
|
161
|
+
platform = normalize_list(args.platform)
|
|
162
|
+
priority = normalize_list(args.priority)
|
|
163
|
+
assignment_type = normalize_list(args.assignment_type)
|
|
164
|
+
sentiment = normalize_list(args.sentiment)
|
|
165
|
+
interaction_state = normalize_list(args.interaction_state)
|
|
166
|
+
if status:
|
|
167
|
+
params["status"] = status
|
|
168
|
+
if channel:
|
|
169
|
+
params["channel"] = channel
|
|
170
|
+
if tags:
|
|
171
|
+
params["tags"] = tags
|
|
172
|
+
if platform:
|
|
173
|
+
params["platform"] = platform
|
|
174
|
+
if args.since:
|
|
175
|
+
params["since"] = parse_iso_datetime(args.since, "since")
|
|
176
|
+
if args.limit:
|
|
177
|
+
params["limit"] = args.limit
|
|
178
|
+
if args.cursor:
|
|
179
|
+
params["cursor"] = args.cursor
|
|
180
|
+
if args.sort_by:
|
|
181
|
+
params["sort_by"] = args.sort_by
|
|
182
|
+
if args.sort_order:
|
|
183
|
+
params["sort_order"] = args.sort_order
|
|
184
|
+
if assignment_type:
|
|
185
|
+
params["assignment_type"] = assignment_type
|
|
186
|
+
if args.assigned_to_id:
|
|
187
|
+
params["assigned_to_id"] = args.assigned_to_id
|
|
188
|
+
if args.assigned_to_type:
|
|
189
|
+
params["assigned_to_type"] = args.assigned_to_type
|
|
190
|
+
if priority:
|
|
191
|
+
params["priority"] = priority
|
|
192
|
+
if args.is_marked is not None:
|
|
193
|
+
params["is_marked"] = args.is_marked
|
|
194
|
+
if args.is_muted is not None:
|
|
195
|
+
params["is_muted"] = args.is_muted
|
|
196
|
+
if sentiment:
|
|
197
|
+
params["sentiment"] = sentiment
|
|
198
|
+
if interaction_state:
|
|
199
|
+
params["interaction_state"] = interaction_state
|
|
200
|
+
if args.has_unread is not None:
|
|
201
|
+
params["has_unread"] = args.has_unread
|
|
202
|
+
if args.unread_participant_type:
|
|
203
|
+
params["unread_participant_type"] = args.unread_participant_type
|
|
204
|
+
if args.fingerprint_id:
|
|
205
|
+
params["fingerprint_id"] = args.fingerprint_id
|
|
206
|
+
if args.pane_assignee_id:
|
|
207
|
+
params["pane_assignee_id"] = args.pane_assignee_id
|
|
208
|
+
if args.view_id:
|
|
209
|
+
params["view_id"] = args.view_id
|
|
210
|
+
if args.queue_label:
|
|
211
|
+
params["queue_label"] = args.queue_label
|
|
212
|
+
return params
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _resolve_summary(summary_text: str | None, summary_json: str | None) -> JsonValue | None:
|
|
216
|
+
if summary_text and summary_json:
|
|
217
|
+
raise ValidationError("Provide either summary or summary_json, not both")
|
|
218
|
+
if summary_json:
|
|
219
|
+
summary = parse_json(summary_json, "summary_json")
|
|
220
|
+
if not isinstance(summary, Mapping):
|
|
221
|
+
raise ValidationError("summary_json must be an object")
|
|
222
|
+
return summary
|
|
223
|
+
if summary_text:
|
|
224
|
+
return summary_text.strip() or None
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _validate_interaction_state(value: str) -> None:
|
|
229
|
+
allowed = {
|
|
230
|
+
"none",
|
|
231
|
+
"awaiting_visitor",
|
|
232
|
+
"awaiting_agent",
|
|
233
|
+
"awaiting_tool",
|
|
234
|
+
"completed",
|
|
235
|
+
"cancelled",
|
|
236
|
+
"timed_out",
|
|
237
|
+
}
|
|
238
|
+
if value.strip().lower() not in allowed:
|
|
239
|
+
raise ValidationError(f"Invalid interaction state: {value}")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _extract_items(payload: JsonValue) -> List[Mapping[str, JsonValue]]:
|
|
243
|
+
if not isinstance(payload, Mapping):
|
|
244
|
+
raise ApiError("Unexpected response payload")
|
|
245
|
+
items = payload.get("items")
|
|
246
|
+
if not isinstance(items, list):
|
|
247
|
+
return []
|
|
248
|
+
return [item for item in items if isinstance(item, Mapping)]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _print_cursor(payload: JsonValue) -> None:
|
|
252
|
+
if not isinstance(payload, Mapping):
|
|
253
|
+
return
|
|
254
|
+
next_cursor = payload.get("nextCursor") or payload.get("next_cursor")
|
|
255
|
+
if isinstance(next_cursor, str) and next_cursor:
|
|
256
|
+
print(f"Next cursor: {next_cursor}")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _add_conversations_list(conv_sub: argparse._SubParsersAction) -> None:
|
|
260
|
+
parser = conv_sub.add_parser("list", help="List conversations")
|
|
261
|
+
parser.add_argument("--status", action="append")
|
|
262
|
+
parser.add_argument("--channel", action="append")
|
|
263
|
+
parser.add_argument("--tags", action="append")
|
|
264
|
+
parser.add_argument("--platform", action="append")
|
|
265
|
+
parser.add_argument("--since")
|
|
266
|
+
parser.add_argument("--limit", type=int)
|
|
267
|
+
parser.add_argument("--cursor")
|
|
268
|
+
parser.add_argument("--sort-by", dest="sort_by")
|
|
269
|
+
parser.add_argument("--sort-order", dest="sort_order")
|
|
270
|
+
parser.add_argument("--assignment-type", dest="assignment_type", action="append")
|
|
271
|
+
parser.add_argument("--assigned-to-id", dest="assigned_to_id")
|
|
272
|
+
parser.add_argument("--assigned-to-type", dest="assigned_to_type")
|
|
273
|
+
parser.add_argument("--priority", action="append")
|
|
274
|
+
parser.add_argument("--sentiment", action="append")
|
|
275
|
+
parser.add_argument("--is-marked", dest="is_marked", type=_parse_bool_flag)
|
|
276
|
+
parser.add_argument("--is-muted", dest="is_muted", type=_parse_bool_flag)
|
|
277
|
+
parser.add_argument("--interaction-state", dest="interaction_state", action="append")
|
|
278
|
+
parser.add_argument("--has-unread", dest="has_unread", type=_parse_bool_flag)
|
|
279
|
+
parser.add_argument("--unread-participant-type", dest="unread_participant_type")
|
|
280
|
+
parser.add_argument("--fingerprint-id", dest="fingerprint_id")
|
|
281
|
+
parser.add_argument("--pane-assignee-id", dest="pane_assignee_id")
|
|
282
|
+
parser.add_argument("--view-id", dest="view_id")
|
|
283
|
+
parser.add_argument("--queue-label", dest="queue_label")
|
|
284
|
+
_add_json_flag(parser)
|
|
285
|
+
parser.set_defaults(handler=handle_conversations_list)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _add_conversation_get(conv_sub: argparse._SubParsersAction) -> None:
|
|
289
|
+
parser = conv_sub.add_parser("get", help="Get conversation details")
|
|
290
|
+
parser.add_argument("conversation_id")
|
|
291
|
+
parser.add_argument("--no-transcript", action="store_true")
|
|
292
|
+
_add_json_flag(parser)
|
|
293
|
+
parser.set_defaults(handler=handle_conversation_get)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _add_conversation_history(conv_sub: argparse._SubParsersAction) -> None:
|
|
297
|
+
parser = conv_sub.add_parser("history", help="Get conversation history by fingerprint")
|
|
298
|
+
parser.add_argument("fingerprint_id")
|
|
299
|
+
parser.set_defaults(handler=handle_conversation_history)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _add_conversation_filters(conv_sub: argparse._SubParsersAction) -> None:
|
|
303
|
+
parser = conv_sub.add_parser("filters", help="List available conversation filters")
|
|
304
|
+
parser.set_defaults(handler=handle_conversation_filters)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _add_conversation_interactions(conv_sub: argparse._SubParsersAction) -> None:
|
|
308
|
+
parser = conv_sub.add_parser("automation-interactions", help="List automation interactions")
|
|
309
|
+
parser.add_argument("conversation_id")
|
|
310
|
+
parser.set_defaults(handler=handle_conversation_interactions)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _add_conversation_assign(conv_sub: argparse._SubParsersAction) -> None:
|
|
314
|
+
parser = conv_sub.add_parser("assign", help="Assign conversation")
|
|
315
|
+
parser.add_argument("conversation_id")
|
|
316
|
+
parser.add_argument("--assignment-type", dest="assignment_type", required=True)
|
|
317
|
+
parser.add_argument("--assigned-to-id", dest="assigned_to_id")
|
|
318
|
+
parser.add_argument("--queue-label", dest="queue_label")
|
|
319
|
+
parser.add_argument("--reason")
|
|
320
|
+
parser.set_defaults(handler=handle_conversation_assign)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _add_conversation_mark(conv_sub: argparse._SubParsersAction) -> None:
|
|
324
|
+
parser = conv_sub.add_parser("mark", help="Mark conversation")
|
|
325
|
+
parser.add_argument("conversation_id")
|
|
326
|
+
parser.set_defaults(handler=handle_conversation_mark)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _add_conversation_unmark(conv_sub: argparse._SubParsersAction) -> None:
|
|
330
|
+
parser = conv_sub.add_parser("unmark", help="Unmark conversation")
|
|
331
|
+
parser.add_argument("conversation_id")
|
|
332
|
+
parser.set_defaults(handler=handle_conversation_unmark)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _add_conversation_mute(conv_sub: argparse._SubParsersAction) -> None:
|
|
336
|
+
parser = conv_sub.add_parser("mute", help="Mute conversation")
|
|
337
|
+
parser.add_argument("conversation_id")
|
|
338
|
+
parser.add_argument("--mute-expires-at", dest="mute_expires_at")
|
|
339
|
+
parser.set_defaults(handler=handle_conversation_mute)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _add_conversation_unmute(conv_sub: argparse._SubParsersAction) -> None:
|
|
343
|
+
parser = conv_sub.add_parser("unmute", help="Unmute conversation")
|
|
344
|
+
parser.add_argument("conversation_id")
|
|
345
|
+
parser.set_defaults(handler=handle_conversation_unmute)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _add_conversation_close(conv_sub: argparse._SubParsersAction) -> None:
|
|
349
|
+
parser = conv_sub.add_parser("close", help="Close conversation")
|
|
350
|
+
parser.add_argument("conversation_id")
|
|
351
|
+
parser.add_argument("--summary")
|
|
352
|
+
parser.add_argument("--summary-json", dest="summary_json")
|
|
353
|
+
parser.add_argument("--duration-seconds", dest="duration_seconds", type=int)
|
|
354
|
+
parser.add_argument("--metadata-updates", dest="metadata_updates")
|
|
355
|
+
parser.set_defaults(handler=handle_conversation_close)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _add_conversation_interaction_state(conv_sub: argparse._SubParsersAction) -> None:
|
|
359
|
+
parser = conv_sub.add_parser("interaction-state", help="Update interaction state")
|
|
360
|
+
parser.add_argument("conversation_id")
|
|
361
|
+
parser.add_argument("--state", required=True)
|
|
362
|
+
parser.add_argument("--interaction-id", dest="interaction_id")
|
|
363
|
+
parser.add_argument("--reason")
|
|
364
|
+
parser.set_defaults(handler=handle_conversation_interaction_state)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _add_conversation_metrics(conv_sub: argparse._SubParsersAction) -> None:
|
|
368
|
+
parser = conv_sub.add_parser("metrics", help="Fetch conversation metrics")
|
|
369
|
+
parser.add_argument("--hours", type=int, default=24)
|
|
370
|
+
parser.add_argument("--metric", action="append")
|
|
371
|
+
parser.add_argument("--agent-id", dest="metric_agent_id")
|
|
372
|
+
parser.add_argument("--channel", dest="metric_channel")
|
|
373
|
+
parser.set_defaults(handler=handle_conversation_metrics)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _add_agent_reply(replies_sub: argparse._SubParsersAction) -> None:
|
|
377
|
+
parser = replies_sub.add_parser("send", help="Send agent reply")
|
|
378
|
+
parser.add_argument("conversation_id")
|
|
379
|
+
parser.add_argument("message")
|
|
380
|
+
parser.add_argument("--metadata")
|
|
381
|
+
_add_json_flag(parser)
|
|
382
|
+
parser.set_defaults(handler=handle_agent_reply)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def register(subparsers: argparse._SubParsersAction) -> None:
|
|
386
|
+
conversations = subparsers.add_parser("conversations", help="Conversation operations")
|
|
387
|
+
conv_sub = conversations.add_subparsers(dest="conversations_command", required=True)
|
|
388
|
+
_add_conversations_list(conv_sub)
|
|
389
|
+
_add_conversation_get(conv_sub)
|
|
390
|
+
_add_conversation_history(conv_sub)
|
|
391
|
+
_add_conversation_filters(conv_sub)
|
|
392
|
+
_add_conversation_interactions(conv_sub)
|
|
393
|
+
_add_conversation_assign(conv_sub)
|
|
394
|
+
_add_conversation_mark(conv_sub)
|
|
395
|
+
_add_conversation_unmark(conv_sub)
|
|
396
|
+
_add_conversation_mute(conv_sub)
|
|
397
|
+
_add_conversation_unmute(conv_sub)
|
|
398
|
+
_add_conversation_close(conv_sub)
|
|
399
|
+
_add_conversation_interaction_state(conv_sub)
|
|
400
|
+
_add_conversation_metrics(conv_sub)
|
|
401
|
+
|
|
402
|
+
replies = subparsers.add_parser("agent-replies", help="Send agent replies")
|
|
403
|
+
replies_sub = replies.add_subparsers(dest="replies_command", required=True)
|
|
404
|
+
_add_agent_reply(replies_sub)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
from guzli.errors import ConfigError
|
|
7
|
+
from guzli.runtime import RuntimeContext
|
|
8
|
+
from guzli.validators import require_non_empty
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def handle_integrations_connect_twilio(context: RuntimeContext, args: argparse.Namespace) -> None:
|
|
12
|
+
organization_id = (
|
|
13
|
+
getattr(args, "organization_id", None) or context.organization_id or ""
|
|
14
|
+
).strip()
|
|
15
|
+
if not organization_id:
|
|
16
|
+
raise ConfigError(
|
|
17
|
+
"organization_id is required — set it via `guzli auth login --organization-id …`."
|
|
18
|
+
)
|
|
19
|
+
body: Dict[str, object] = {
|
|
20
|
+
"account_sid": require_non_empty(args.account_sid, "account_sid"),
|
|
21
|
+
"auth_token": require_non_empty(args.auth_token, "auth_token"),
|
|
22
|
+
}
|
|
23
|
+
if args.label:
|
|
24
|
+
body["label"] = args.label
|
|
25
|
+
if args.agent_id:
|
|
26
|
+
body["agent_id"] = args.agent_id
|
|
27
|
+
payload = context.integrations.connect_twilio(organization_id, body)
|
|
28
|
+
context.renderer.print_json(payload)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _add_integrations_connect_twilio(sub: argparse._SubParsersAction) -> None:
|
|
32
|
+
parser = sub.add_parser(
|
|
33
|
+
"connect-twilio",
|
|
34
|
+
help="Connect a Twilio integration (creates the telephony account Campaign Launch needs)",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--account-sid", dest="account_sid", required=True, help="Twilio Account SID (AC…)"
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument("--auth-token", dest="auth_token", required=True, help="Twilio auth token")
|
|
40
|
+
parser.add_argument("--label", help="Optional display label for the account")
|
|
41
|
+
parser.add_argument("--agent-id", dest="agent_id", help="Optional agent binding")
|
|
42
|
+
parser.set_defaults(handler=handle_integrations_connect_twilio)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def register(subparsers: argparse._SubParsersAction) -> None:
|
|
46
|
+
integrations = subparsers.add_parser("integrations", help="Connect external integrations")
|
|
47
|
+
integ_sub = integrations.add_subparsers(dest="integrations_command", required=True)
|
|
48
|
+
_add_integrations_connect_twilio(integ_sub)
|