applied-cli 0.1.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.
- applied_cli/__init__.py +2 -0
- applied_cli/auth_store.py +263 -0
- applied_cli/commands/__init__.py +2 -0
- applied_cli/commands/_hints.py +11 -0
- applied_cli/commands/_normalize.py +79 -0
- applied_cli/commands/_parsers.py +58 -0
- applied_cli/commands/_ui.py +33 -0
- applied_cli/commands/agent.py +1231 -0
- applied_cli/commands/auth.py +739 -0
- applied_cli/commands/chat.py +379 -0
- applied_cli/commands/coverage.py +348 -0
- applied_cli/commands/discover.py +1006 -0
- applied_cli/commands/fix.py +1204 -0
- applied_cli/commands/insights.py +614 -0
- applied_cli/commands/intents.py +447 -0
- applied_cli/commands/rate.py +508 -0
- applied_cli/commands/responses.py +604 -0
- applied_cli/commands/shop.py +1757 -0
- applied_cli/commands/simulate.py +330 -0
- applied_cli/commands/spec.py +238 -0
- applied_cli/config.py +50 -0
- applied_cli/error_reporting.py +38 -0
- applied_cli/http.py +1614 -0
- applied_cli/main.py +90 -0
- applied_cli/mcp_server.py +738 -0
- applied_cli/presets/demo.yaml +170 -0
- applied_cli/runtime.py +53 -0
- applied_cli/shop_spec.py +398 -0
- applied_cli/spec_workflow.py +432 -0
- applied_cli-0.1.0.dist-info/METADATA +176 -0
- applied_cli-0.1.0.dist-info/RECORD +34 -0
- applied_cli-0.1.0.dist-info/WHEEL +5 -0
- applied_cli-0.1.0.dist-info/entry_points.txt +3 -0
- applied_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from applied_cli.commands._parsers import (
|
|
8
|
+
parse_csv_list,
|
|
9
|
+
parse_optional_bool,
|
|
10
|
+
validate_uuid,
|
|
11
|
+
validate_uuid_list,
|
|
12
|
+
)
|
|
13
|
+
from applied_cli.error_reporting import render_api_error
|
|
14
|
+
from applied_cli.http import (
|
|
15
|
+
APIError,
|
|
16
|
+
get_insights_report,
|
|
17
|
+
get_insights_status,
|
|
18
|
+
insights_followup,
|
|
19
|
+
insights_generate,
|
|
20
|
+
list_insights_reports,
|
|
21
|
+
)
|
|
22
|
+
from applied_cli.runtime import resolve_runtime
|
|
23
|
+
|
|
24
|
+
app = typer.Typer(help="Generate and inspect analytics insights reports.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _emit(payload: Any, output_json: bool) -> None:
|
|
28
|
+
if output_json:
|
|
29
|
+
typer.echo(json.dumps(payload, indent=2, default=str))
|
|
30
|
+
return
|
|
31
|
+
if isinstance(payload, list):
|
|
32
|
+
if not payload:
|
|
33
|
+
typer.echo("No results.")
|
|
34
|
+
return
|
|
35
|
+
for row in payload:
|
|
36
|
+
if not isinstance(row, dict):
|
|
37
|
+
typer.echo(str(row))
|
|
38
|
+
continue
|
|
39
|
+
items: list[str] = []
|
|
40
|
+
report_id = row.get("reportId") or row.get("id")
|
|
41
|
+
if report_id:
|
|
42
|
+
items.append(f"report_id={report_id}")
|
|
43
|
+
title = row.get("title")
|
|
44
|
+
if title:
|
|
45
|
+
items.append(f"title={title}")
|
|
46
|
+
status = row.get("status")
|
|
47
|
+
if status:
|
|
48
|
+
items.append(f"status={status}")
|
|
49
|
+
findings = row.get("findingsCount")
|
|
50
|
+
if findings is not None:
|
|
51
|
+
items.append(f"findings={findings}")
|
|
52
|
+
conversations = row.get("conversationsAnalyzed")
|
|
53
|
+
if conversations is not None:
|
|
54
|
+
items.append(f"conversations={conversations}")
|
|
55
|
+
typer.echo(" | ".join(items) if items else str(row))
|
|
56
|
+
return
|
|
57
|
+
if isinstance(payload, dict):
|
|
58
|
+
items = []
|
|
59
|
+
report_id = payload.get("reportId")
|
|
60
|
+
if report_id:
|
|
61
|
+
items.append(f"report_id={report_id}")
|
|
62
|
+
status = payload.get("status")
|
|
63
|
+
if status:
|
|
64
|
+
items.append(f"status={status}")
|
|
65
|
+
title = payload.get("title")
|
|
66
|
+
if title:
|
|
67
|
+
items.append(f"title={title}")
|
|
68
|
+
findings = payload.get("findingsCount")
|
|
69
|
+
if findings is not None:
|
|
70
|
+
items.append(f"findings={findings}")
|
|
71
|
+
conversations = payload.get("conversationsAnalyzed")
|
|
72
|
+
if conversations is not None:
|
|
73
|
+
items.append(f"conversations={conversations}")
|
|
74
|
+
if items:
|
|
75
|
+
typer.echo("result=success | " + " | ".join(items))
|
|
76
|
+
return
|
|
77
|
+
typer.echo(str(payload))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _build_filters(
|
|
81
|
+
*,
|
|
82
|
+
agent_ids: list[str],
|
|
83
|
+
topic_ids: list[str],
|
|
84
|
+
intent_ids: list[str],
|
|
85
|
+
resolutions: list[str],
|
|
86
|
+
conversation_types: list[str],
|
|
87
|
+
user_ids: list[str],
|
|
88
|
+
tags: list[str],
|
|
89
|
+
directions: list[str],
|
|
90
|
+
flags: list[str],
|
|
91
|
+
score_gte: Optional[int],
|
|
92
|
+
score_lte: Optional[int],
|
|
93
|
+
is_test: Optional[bool],
|
|
94
|
+
) -> dict[str, Any]:
|
|
95
|
+
filters: dict[str, Any] = {}
|
|
96
|
+
if agent_ids:
|
|
97
|
+
filters["agent__in"] = ",".join(agent_ids)
|
|
98
|
+
if topic_ids:
|
|
99
|
+
filters["label__in"] = ",".join(topic_ids)
|
|
100
|
+
if intent_ids:
|
|
101
|
+
filters["sublabel__in"] = ",".join(intent_ids)
|
|
102
|
+
if resolutions:
|
|
103
|
+
filters["resolution__in"] = ",".join(resolutions)
|
|
104
|
+
if conversation_types:
|
|
105
|
+
filters["type__in"] = ",".join(conversation_types)
|
|
106
|
+
if user_ids:
|
|
107
|
+
filters["user__in"] = ",".join(user_ids)
|
|
108
|
+
if tags:
|
|
109
|
+
filters["tags__in"] = ",".join(tags)
|
|
110
|
+
if directions:
|
|
111
|
+
filters["direction__in"] = ",".join(directions)
|
|
112
|
+
if flags:
|
|
113
|
+
filters["flags__in"] = ",".join(flags)
|
|
114
|
+
if score_gte is not None:
|
|
115
|
+
filters["score__gte"] = score_gte
|
|
116
|
+
if score_lte is not None:
|
|
117
|
+
filters["score__lte"] = score_lte
|
|
118
|
+
if is_test is not None:
|
|
119
|
+
filters["is_test"] = is_test
|
|
120
|
+
return filters
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _wait_for_report_completion(
|
|
124
|
+
*,
|
|
125
|
+
base_url: str,
|
|
126
|
+
shop_id: str,
|
|
127
|
+
api_token: str,
|
|
128
|
+
report_id: str,
|
|
129
|
+
poll_seconds: float,
|
|
130
|
+
max_wait_seconds: float,
|
|
131
|
+
) -> dict[str, Any]:
|
|
132
|
+
deadline = time.monotonic() + max_wait_seconds
|
|
133
|
+
last_status: dict[str, Any] = {}
|
|
134
|
+
while time.monotonic() < deadline:
|
|
135
|
+
try:
|
|
136
|
+
last_status = get_insights_status(
|
|
137
|
+
base_url=base_url,
|
|
138
|
+
shop_id=shop_id,
|
|
139
|
+
api_token=api_token,
|
|
140
|
+
report_id=report_id,
|
|
141
|
+
)
|
|
142
|
+
except APIError as exc:
|
|
143
|
+
typer.echo(render_api_error(exc, action="poll insights status"), err=True)
|
|
144
|
+
raise typer.Exit(code=1) from exc
|
|
145
|
+
|
|
146
|
+
status_value = str(last_status.get("status") or "").lower()
|
|
147
|
+
if status_value in {"completed", "failed"}:
|
|
148
|
+
return last_status
|
|
149
|
+
time.sleep(poll_seconds)
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
"reportId": report_id,
|
|
153
|
+
"status": "generating",
|
|
154
|
+
"message": "Timed out waiting for completion. Re-run `applied-cli insights status --report-id <uuid>`.",
|
|
155
|
+
"last_status": last_status,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _wait_and_emit_report(
|
|
160
|
+
*,
|
|
161
|
+
base_url: str,
|
|
162
|
+
shop_id: str,
|
|
163
|
+
api_token: str,
|
|
164
|
+
report_id: str,
|
|
165
|
+
poll_seconds: float,
|
|
166
|
+
max_wait_seconds: float,
|
|
167
|
+
output_json: bool,
|
|
168
|
+
) -> None:
|
|
169
|
+
status_payload = _wait_for_report_completion(
|
|
170
|
+
base_url=base_url,
|
|
171
|
+
shop_id=shop_id,
|
|
172
|
+
api_token=api_token,
|
|
173
|
+
report_id=report_id,
|
|
174
|
+
poll_seconds=poll_seconds,
|
|
175
|
+
max_wait_seconds=max_wait_seconds,
|
|
176
|
+
)
|
|
177
|
+
status_value = str(status_payload.get("status") or "").lower()
|
|
178
|
+
if status_value == "completed":
|
|
179
|
+
try:
|
|
180
|
+
report = get_insights_report(
|
|
181
|
+
base_url=base_url,
|
|
182
|
+
shop_id=shop_id,
|
|
183
|
+
api_token=api_token,
|
|
184
|
+
report_id=report_id,
|
|
185
|
+
)
|
|
186
|
+
except APIError as exc:
|
|
187
|
+
typer.echo(render_api_error(exc, action="fetch insights report"), err=True)
|
|
188
|
+
raise typer.Exit(code=1) from exc
|
|
189
|
+
_emit(report, output_json)
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
_emit(status_payload, output_json)
|
|
193
|
+
raise typer.Exit(code=1)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@app.command(
|
|
197
|
+
"run",
|
|
198
|
+
help=(
|
|
199
|
+
"Generate an insights report. Example: applied-cli insights run "
|
|
200
|
+
"--query 'top issues in last 30 days' --agent-ids <uuid> --wait"
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
def run_cmd(
|
|
204
|
+
query: Optional[str] = typer.Option(
|
|
205
|
+
None,
|
|
206
|
+
"--query",
|
|
207
|
+
"--instruction",
|
|
208
|
+
help="Optional natural-language analysis question.",
|
|
209
|
+
),
|
|
210
|
+
date_range: str = typer.Option(
|
|
211
|
+
"relative:-14d,",
|
|
212
|
+
"--date-range",
|
|
213
|
+
help="Date range, e.g. relative:-30d, or ISO start,end.",
|
|
214
|
+
),
|
|
215
|
+
data_source: str = typer.Option(
|
|
216
|
+
"conversations",
|
|
217
|
+
"--data-source",
|
|
218
|
+
help="Data source: conversations or tickets.",
|
|
219
|
+
),
|
|
220
|
+
agent_ids_raw: Optional[str] = typer.Option(
|
|
221
|
+
None,
|
|
222
|
+
"--agent-ids",
|
|
223
|
+
help="Comma-separated agent UUIDs.",
|
|
224
|
+
),
|
|
225
|
+
topic_ids_raw: Optional[str] = typer.Option(
|
|
226
|
+
None,
|
|
227
|
+
"--topic-ids",
|
|
228
|
+
help="Comma-separated topic UUIDs.",
|
|
229
|
+
),
|
|
230
|
+
intent_ids_raw: Optional[str] = typer.Option(
|
|
231
|
+
None,
|
|
232
|
+
"--intent-ids",
|
|
233
|
+
help="Comma-separated intent UUIDs.",
|
|
234
|
+
),
|
|
235
|
+
resolutions_raw: Optional[str] = typer.Option(
|
|
236
|
+
None,
|
|
237
|
+
"--resolutions",
|
|
238
|
+
help="Comma-separated values like escalated,human,soft,hard.",
|
|
239
|
+
),
|
|
240
|
+
conversation_types_raw: Optional[str] = typer.Option(
|
|
241
|
+
None,
|
|
242
|
+
"--types",
|
|
243
|
+
help="Comma-separated conversation types: web_chat,email,sms,phone_call,web_call,comments,form.",
|
|
244
|
+
),
|
|
245
|
+
user_ids_raw: Optional[str] = typer.Option(
|
|
246
|
+
None,
|
|
247
|
+
"--user-ids",
|
|
248
|
+
help="Comma-separated assignee/user UUIDs.",
|
|
249
|
+
),
|
|
250
|
+
tags_raw: Optional[str] = typer.Option(
|
|
251
|
+
None,
|
|
252
|
+
"--tags",
|
|
253
|
+
help="Comma-separated tags.",
|
|
254
|
+
),
|
|
255
|
+
directions_raw: Optional[str] = typer.Option(
|
|
256
|
+
None,
|
|
257
|
+
"--directions",
|
|
258
|
+
help="Comma-separated directions (e.g. inbound,outbound).",
|
|
259
|
+
),
|
|
260
|
+
flags_raw: Optional[str] = typer.Option(
|
|
261
|
+
None,
|
|
262
|
+
"--flags",
|
|
263
|
+
help="Comma-separated conversation flags.",
|
|
264
|
+
),
|
|
265
|
+
score_gte: Optional[int] = typer.Option(
|
|
266
|
+
None,
|
|
267
|
+
"--score-gte",
|
|
268
|
+
help="Minimum CSAT score.",
|
|
269
|
+
),
|
|
270
|
+
score_lte: Optional[int] = typer.Option(
|
|
271
|
+
None,
|
|
272
|
+
"--score-lte",
|
|
273
|
+
help="Maximum CSAT score.",
|
|
274
|
+
),
|
|
275
|
+
is_test_raw: Optional[str] = typer.Option(
|
|
276
|
+
None,
|
|
277
|
+
"--is-test",
|
|
278
|
+
help="Filter test conversations: true/false.",
|
|
279
|
+
),
|
|
280
|
+
conversation_ids_raw: Optional[str] = typer.Option(
|
|
281
|
+
None,
|
|
282
|
+
"--conversation-ids",
|
|
283
|
+
help="Optional comma-separated conversation UUIDs to scope report.",
|
|
284
|
+
),
|
|
285
|
+
wait: bool = typer.Option(
|
|
286
|
+
False,
|
|
287
|
+
"--wait/--no-wait",
|
|
288
|
+
help="Poll until completed/failed.",
|
|
289
|
+
),
|
|
290
|
+
poll_seconds: float = typer.Option(
|
|
291
|
+
5.0,
|
|
292
|
+
"--poll-seconds",
|
|
293
|
+
help="Polling interval when --wait is set.",
|
|
294
|
+
),
|
|
295
|
+
max_wait_seconds: float = typer.Option(
|
|
296
|
+
600.0,
|
|
297
|
+
"--max-wait-seconds",
|
|
298
|
+
help="Max wait duration for --wait.",
|
|
299
|
+
),
|
|
300
|
+
output_json: bool = typer.Option(False, "--json", help="Emit JSON output."),
|
|
301
|
+
base_url: Optional[str] = typer.Option(None, help="Applied base URL."),
|
|
302
|
+
shop_id: Optional[str] = typer.Option(None, help="Target shop UUID."),
|
|
303
|
+
api_token: Optional[str] = typer.Option(None, help="Applied API token."),
|
|
304
|
+
) -> None:
|
|
305
|
+
if data_source not in {"conversations", "tickets"}:
|
|
306
|
+
raise typer.BadParameter("data-source must be one of: conversations, tickets.")
|
|
307
|
+
if poll_seconds <= 0:
|
|
308
|
+
raise typer.BadParameter("poll-seconds must be > 0.")
|
|
309
|
+
if max_wait_seconds <= 0:
|
|
310
|
+
raise typer.BadParameter("max-wait-seconds must be > 0.")
|
|
311
|
+
if score_gte is not None and score_lte is not None and score_gte > score_lte:
|
|
312
|
+
raise typer.BadParameter("score-gte cannot be greater than score-lte.")
|
|
313
|
+
|
|
314
|
+
agent_ids = parse_csv_list(agent_ids_raw)
|
|
315
|
+
topic_ids = parse_csv_list(topic_ids_raw)
|
|
316
|
+
intent_ids = parse_csv_list(intent_ids_raw)
|
|
317
|
+
user_ids = parse_csv_list(user_ids_raw)
|
|
318
|
+
conversation_ids = parse_csv_list(conversation_ids_raw)
|
|
319
|
+
resolutions = parse_csv_list(resolutions_raw)
|
|
320
|
+
conversation_types = parse_csv_list(conversation_types_raw)
|
|
321
|
+
tags = parse_csv_list(tags_raw)
|
|
322
|
+
directions = parse_csv_list(directions_raw)
|
|
323
|
+
flags = parse_csv_list(flags_raw)
|
|
324
|
+
is_test = parse_optional_bool(is_test_raw, field_name="is-test")
|
|
325
|
+
|
|
326
|
+
validate_uuid_list(
|
|
327
|
+
[*agent_ids, *topic_ids, *intent_ids, *user_ids, *conversation_ids],
|
|
328
|
+
field_name="uuid",
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
filters = _build_filters(
|
|
332
|
+
agent_ids=agent_ids,
|
|
333
|
+
topic_ids=topic_ids,
|
|
334
|
+
intent_ids=intent_ids,
|
|
335
|
+
resolutions=resolutions,
|
|
336
|
+
conversation_types=conversation_types,
|
|
337
|
+
user_ids=user_ids,
|
|
338
|
+
tags=tags,
|
|
339
|
+
directions=directions,
|
|
340
|
+
flags=flags,
|
|
341
|
+
score_gte=score_gte,
|
|
342
|
+
score_lte=score_lte,
|
|
343
|
+
is_test=is_test,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
resolved_base_url, resolved_shop_id, resolved_token = resolve_runtime(
|
|
348
|
+
base_url=base_url,
|
|
349
|
+
shop_id=shop_id,
|
|
350
|
+
api_token=api_token,
|
|
351
|
+
)
|
|
352
|
+
generated = insights_generate(
|
|
353
|
+
base_url=resolved_base_url,
|
|
354
|
+
shop_id=resolved_shop_id,
|
|
355
|
+
api_token=resolved_token,
|
|
356
|
+
instruction=query,
|
|
357
|
+
date_range=date_range,
|
|
358
|
+
filters=filters if filters else None,
|
|
359
|
+
conversation_ids=conversation_ids if conversation_ids else None,
|
|
360
|
+
data_source=data_source,
|
|
361
|
+
)
|
|
362
|
+
except APIError as exc:
|
|
363
|
+
typer.echo(render_api_error(exc, action="run insights report"), err=True)
|
|
364
|
+
raise typer.Exit(code=1) from exc
|
|
365
|
+
|
|
366
|
+
report_id = str(generated.get("reportId") or "")
|
|
367
|
+
if not wait or not report_id:
|
|
368
|
+
_emit(generated, output_json)
|
|
369
|
+
return
|
|
370
|
+
|
|
371
|
+
_wait_and_emit_report(
|
|
372
|
+
base_url=resolved_base_url,
|
|
373
|
+
shop_id=resolved_shop_id,
|
|
374
|
+
api_token=resolved_token,
|
|
375
|
+
report_id=report_id,
|
|
376
|
+
poll_seconds=poll_seconds,
|
|
377
|
+
max_wait_seconds=max_wait_seconds,
|
|
378
|
+
output_json=output_json,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@app.command(
|
|
383
|
+
"followup",
|
|
384
|
+
help=(
|
|
385
|
+
"Run a follow-up insights report on a parent report thread. Example: "
|
|
386
|
+
"applied-cli insights followup --report-id <uuid> --query "
|
|
387
|
+
"'break down refund complaints by intent' --wait"
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
def followup_cmd(
|
|
391
|
+
report_id: str = typer.Option(
|
|
392
|
+
...,
|
|
393
|
+
"--report-id",
|
|
394
|
+
"--parent-report-id",
|
|
395
|
+
"--id",
|
|
396
|
+
help="Parent insights report UUID.",
|
|
397
|
+
),
|
|
398
|
+
query: str = typer.Option(
|
|
399
|
+
...,
|
|
400
|
+
"--query",
|
|
401
|
+
"--instruction",
|
|
402
|
+
help="Follow-up analysis question.",
|
|
403
|
+
),
|
|
404
|
+
date_range: Optional[str] = typer.Option(
|
|
405
|
+
None,
|
|
406
|
+
"--date-range",
|
|
407
|
+
help="Optional override date range, e.g. relative:-30d, or ISO start,end.",
|
|
408
|
+
),
|
|
409
|
+
conversation_ids_raw: Optional[str] = typer.Option(
|
|
410
|
+
None,
|
|
411
|
+
"--conversation-ids",
|
|
412
|
+
help="Optional comma-separated conversation UUIDs to scope follow-up.",
|
|
413
|
+
),
|
|
414
|
+
wait: bool = typer.Option(
|
|
415
|
+
False,
|
|
416
|
+
"--wait/--no-wait",
|
|
417
|
+
help="Poll until completed/failed.",
|
|
418
|
+
),
|
|
419
|
+
poll_seconds: float = typer.Option(
|
|
420
|
+
5.0,
|
|
421
|
+
"--poll-seconds",
|
|
422
|
+
help="Polling interval when --wait is set.",
|
|
423
|
+
),
|
|
424
|
+
max_wait_seconds: float = typer.Option(
|
|
425
|
+
600.0,
|
|
426
|
+
"--max-wait-seconds",
|
|
427
|
+
help="Max wait duration for --wait.",
|
|
428
|
+
),
|
|
429
|
+
output_json: bool = typer.Option(False, "--json", help="Emit JSON output."),
|
|
430
|
+
base_url: Optional[str] = typer.Option(None, help="Applied base URL."),
|
|
431
|
+
shop_id: Optional[str] = typer.Option(None, help="Target shop UUID."),
|
|
432
|
+
api_token: Optional[str] = typer.Option(None, help="Applied API token."),
|
|
433
|
+
) -> None:
|
|
434
|
+
if not query.strip():
|
|
435
|
+
raise typer.BadParameter("query cannot be empty.")
|
|
436
|
+
if poll_seconds <= 0:
|
|
437
|
+
raise typer.BadParameter("poll-seconds must be > 0.")
|
|
438
|
+
if max_wait_seconds <= 0:
|
|
439
|
+
raise typer.BadParameter("max-wait-seconds must be > 0.")
|
|
440
|
+
validate_uuid(report_id, field_name="report-id")
|
|
441
|
+
|
|
442
|
+
conversation_ids = parse_csv_list(conversation_ids_raw)
|
|
443
|
+
validate_uuid_list(conversation_ids, field_name="conversation-id")
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
resolved_base_url, resolved_shop_id, resolved_token = resolve_runtime(
|
|
447
|
+
base_url=base_url,
|
|
448
|
+
shop_id=shop_id,
|
|
449
|
+
api_token=api_token,
|
|
450
|
+
)
|
|
451
|
+
generated = insights_followup(
|
|
452
|
+
base_url=resolved_base_url,
|
|
453
|
+
shop_id=resolved_shop_id,
|
|
454
|
+
api_token=resolved_token,
|
|
455
|
+
instruction=query,
|
|
456
|
+
parent_report_id=report_id,
|
|
457
|
+
date_range=date_range,
|
|
458
|
+
conversation_ids=conversation_ids if conversation_ids else None,
|
|
459
|
+
)
|
|
460
|
+
except APIError as exc:
|
|
461
|
+
typer.echo(render_api_error(exc, action="run insights follow-up"), err=True)
|
|
462
|
+
raise typer.Exit(code=1) from exc
|
|
463
|
+
|
|
464
|
+
followup_report_id = str(generated.get("reportId") or "")
|
|
465
|
+
if not wait or not followup_report_id:
|
|
466
|
+
_emit(generated, output_json)
|
|
467
|
+
return
|
|
468
|
+
|
|
469
|
+
_wait_and_emit_report(
|
|
470
|
+
base_url=resolved_base_url,
|
|
471
|
+
shop_id=resolved_shop_id,
|
|
472
|
+
api_token=resolved_token,
|
|
473
|
+
report_id=followup_report_id,
|
|
474
|
+
poll_seconds=poll_seconds,
|
|
475
|
+
max_wait_seconds=max_wait_seconds,
|
|
476
|
+
output_json=output_json,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
@app.command(
|
|
481
|
+
"status",
|
|
482
|
+
help=(
|
|
483
|
+
"Check report generation status. Example: applied-cli insights status "
|
|
484
|
+
"--report-id <uuid>"
|
|
485
|
+
),
|
|
486
|
+
)
|
|
487
|
+
def status_cmd(
|
|
488
|
+
report_id: str = typer.Option(
|
|
489
|
+
...,
|
|
490
|
+
"--report-id",
|
|
491
|
+
"--id",
|
|
492
|
+
help="Insights report UUID.",
|
|
493
|
+
),
|
|
494
|
+
output_json: bool = typer.Option(False, "--json", help="Emit JSON output."),
|
|
495
|
+
base_url: Optional[str] = typer.Option(None, help="Applied base URL."),
|
|
496
|
+
shop_id: Optional[str] = typer.Option(None, help="Target shop UUID."),
|
|
497
|
+
api_token: Optional[str] = typer.Option(None, help="Applied API token."),
|
|
498
|
+
) -> None:
|
|
499
|
+
validate_uuid(report_id, field_name="report-id")
|
|
500
|
+
try:
|
|
501
|
+
resolved_base_url, resolved_shop_id, resolved_token = resolve_runtime(
|
|
502
|
+
base_url=base_url,
|
|
503
|
+
shop_id=shop_id,
|
|
504
|
+
api_token=api_token,
|
|
505
|
+
)
|
|
506
|
+
status_data = get_insights_status(
|
|
507
|
+
base_url=resolved_base_url,
|
|
508
|
+
shop_id=resolved_shop_id,
|
|
509
|
+
api_token=resolved_token,
|
|
510
|
+
report_id=report_id,
|
|
511
|
+
)
|
|
512
|
+
except APIError as exc:
|
|
513
|
+
typer.echo(render_api_error(exc, action="get insights status"), err=True)
|
|
514
|
+
raise typer.Exit(code=1) from exc
|
|
515
|
+
_emit(status_data, output_json)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
@app.command(
|
|
519
|
+
"reports",
|
|
520
|
+
help=(
|
|
521
|
+
"List report history. Example: applied-cli insights reports --status completed"
|
|
522
|
+
),
|
|
523
|
+
)
|
|
524
|
+
def reports_cmd(
|
|
525
|
+
status: Optional[str] = typer.Option(
|
|
526
|
+
None,
|
|
527
|
+
"--status",
|
|
528
|
+
help="Optional status filter: generating, completed, failed.",
|
|
529
|
+
),
|
|
530
|
+
configuration_id: Optional[str] = typer.Option(
|
|
531
|
+
None,
|
|
532
|
+
"--configuration-id",
|
|
533
|
+
help="Optional configuration UUID filter.",
|
|
534
|
+
),
|
|
535
|
+
parent_report_id: Optional[str] = typer.Option(
|
|
536
|
+
None,
|
|
537
|
+
"--parent-report-id",
|
|
538
|
+
help="Optional parent report UUID filter.",
|
|
539
|
+
),
|
|
540
|
+
output_json: bool = typer.Option(False, "--json", help="Emit JSON output."),
|
|
541
|
+
base_url: Optional[str] = typer.Option(None, help="Applied base URL."),
|
|
542
|
+
shop_id: Optional[str] = typer.Option(None, help="Target shop UUID."),
|
|
543
|
+
api_token: Optional[str] = typer.Option(None, help="Applied API token."),
|
|
544
|
+
) -> None:
|
|
545
|
+
if status and status not in {"generating", "completed", "failed"}:
|
|
546
|
+
raise typer.BadParameter("status must be one of: generating, completed, failed.")
|
|
547
|
+
if configuration_id:
|
|
548
|
+
validate_uuid(configuration_id, field_name="configuration-id")
|
|
549
|
+
if parent_report_id:
|
|
550
|
+
validate_uuid(parent_report_id, field_name="parent-report-id")
|
|
551
|
+
|
|
552
|
+
try:
|
|
553
|
+
resolved_base_url, resolved_shop_id, resolved_token = resolve_runtime(
|
|
554
|
+
base_url=base_url,
|
|
555
|
+
shop_id=shop_id,
|
|
556
|
+
api_token=api_token,
|
|
557
|
+
)
|
|
558
|
+
rows = list_insights_reports(
|
|
559
|
+
base_url=resolved_base_url,
|
|
560
|
+
shop_id=resolved_shop_id,
|
|
561
|
+
api_token=resolved_token,
|
|
562
|
+
status=status,
|
|
563
|
+
configuration_id=configuration_id,
|
|
564
|
+
parent_report_id=parent_report_id,
|
|
565
|
+
)
|
|
566
|
+
except APIError as exc:
|
|
567
|
+
typer.echo(render_api_error(exc, action="list insights reports"), err=True)
|
|
568
|
+
raise typer.Exit(code=1) from exc
|
|
569
|
+
_emit(rows, output_json)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
@app.command(
|
|
573
|
+
"describe",
|
|
574
|
+
help=(
|
|
575
|
+
"Describe a report including generated findings. Example: applied-cli insights "
|
|
576
|
+
"describe --report-id <uuid>"
|
|
577
|
+
),
|
|
578
|
+
)
|
|
579
|
+
@app.command(
|
|
580
|
+
"show",
|
|
581
|
+
help=(
|
|
582
|
+
"Show a report including generated findings. Example: applied-cli insights show "
|
|
583
|
+
"--report-id <uuid>"
|
|
584
|
+
),
|
|
585
|
+
)
|
|
586
|
+
def show_cmd(
|
|
587
|
+
report_id: str = typer.Option(
|
|
588
|
+
...,
|
|
589
|
+
"--report-id",
|
|
590
|
+
"--id",
|
|
591
|
+
help="Insights report UUID.",
|
|
592
|
+
),
|
|
593
|
+
output_json: bool = typer.Option(False, "--json", help="Emit JSON output."),
|
|
594
|
+
base_url: Optional[str] = typer.Option(None, help="Applied base URL."),
|
|
595
|
+
shop_id: Optional[str] = typer.Option(None, help="Target shop UUID."),
|
|
596
|
+
api_token: Optional[str] = typer.Option(None, help="Applied API token."),
|
|
597
|
+
) -> None:
|
|
598
|
+
validate_uuid(report_id, field_name="report-id")
|
|
599
|
+
try:
|
|
600
|
+
resolved_base_url, resolved_shop_id, resolved_token = resolve_runtime(
|
|
601
|
+
base_url=base_url,
|
|
602
|
+
shop_id=shop_id,
|
|
603
|
+
api_token=api_token,
|
|
604
|
+
)
|
|
605
|
+
payload = get_insights_report(
|
|
606
|
+
base_url=resolved_base_url,
|
|
607
|
+
shop_id=resolved_shop_id,
|
|
608
|
+
api_token=resolved_token,
|
|
609
|
+
report_id=report_id,
|
|
610
|
+
)
|
|
611
|
+
except APIError as exc:
|
|
612
|
+
typer.echo(render_api_error(exc, action="show insights report"), err=True)
|
|
613
|
+
raise typer.Exit(code=1) from exc
|
|
614
|
+
_emit(payload, output_json)
|