applied-cli 0.5.73__tar.gz → 0.5.74__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.
Files changed (28) hide show
  1. {applied_cli-0.5.73 → applied_cli-0.5.74}/PKG-INFO +14 -1
  2. {applied_cli-0.5.73 → applied_cli-0.5.74}/README.md +13 -0
  3. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli/__init__.py +1 -1
  4. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli/client.py +312 -12
  5. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli.egg-info/PKG-INFO +14 -1
  6. {applied_cli-0.5.73 → applied_cli-0.5.74}/pyproject.toml +1 -1
  7. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli/agent_scoped_flows.py +0 -0
  8. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli/cli.py +0 -0
  9. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli/conversation_lookup.py +0 -0
  10. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli/conversations.py +0 -0
  11. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli/credentials.py +0 -0
  12. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli/flow_helpers.py +0 -0
  13. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli/formatters.py +0 -0
  14. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli/tools.py +0 -0
  15. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli.egg-info/SOURCES.txt +0 -0
  16. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli.egg-info/dependency_links.txt +0 -0
  17. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli.egg-info/entry_points.txt +0 -0
  18. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli.egg-info/requires.txt +0 -0
  19. {applied_cli-0.5.73 → applied_cli-0.5.74}/applied_cli.egg-info/top_level.txt +0 -0
  20. {applied_cli-0.5.73 → applied_cli-0.5.74}/setup.cfg +0 -0
  21. {applied_cli-0.5.73 → applied_cli-0.5.74}/tests/test_agent_scoped_flows.py +0 -0
  22. {applied_cli-0.5.73 → applied_cli-0.5.74}/tests/test_audit_tools.py +0 -0
  23. {applied_cli-0.5.73 → applied_cli-0.5.74}/tests/test_benchmark_scenario_tools.py +0 -0
  24. {applied_cli-0.5.73 → applied_cli-0.5.74}/tests/test_cli.py +0 -0
  25. {applied_cli-0.5.73 → applied_cli-0.5.74}/tests/test_client.py +0 -0
  26. {applied_cli-0.5.73 → applied_cli-0.5.74}/tests/test_conversation_tools.py +0 -0
  27. {applied_cli-0.5.73 → applied_cli-0.5.74}/tests/test_flow_tools.py +0 -0
  28. {applied_cli-0.5.73 → applied_cli-0.5.74}/tests/test_knowledge_content_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: applied-cli
3
- Version: 0.5.73
3
+ Version: 0.5.74
4
4
  Summary: CLI and shared client library for Applied Labs AI support agents
5
5
  Author: Applied Labs
6
6
  License-Expression: MIT
@@ -67,8 +67,18 @@ applied knowledge-unprotect <id>
67
67
  # Taxonomy
68
68
  applied taxonomy --type topics
69
69
  applied taxonomy-counts --start 2026-03-01 --end 2026-03-18 --format csv
70
+
71
+ # Analytics
72
+ applied analytics-report --view overview_aggregate_metrics --model conversation --start 2026-04-01 --end 2026-04-30 --format json
73
+ applied analytics --group-by topic --metrics count --start 2026-04-01 --end 2026-04-30 --format json
74
+ applied analytics --group-by intent --metrics count --start 2026-04-01 --end 2026-04-30 --format json
75
+ applied metrics --metric-name conversation.resolve --start 2026-04-01 --end 2026-04-30 --period day --format json
70
76
  ```
71
77
 
78
+ `analytics-report` returns the selected report payload, not necessarily a `{ "rows": [...] }`
79
+ object. `analytics` returns grouped rows and currently supports `--metrics count`.
80
+ Raw analytics SQL is not available through the public CLI surface.
81
+
72
82
  ## Library Usage
73
83
 
74
84
  ```python
@@ -99,6 +109,9 @@ conversations = await tools.conversation_query(
99
109
  | `knowledge_list` | List knowledge base items |
100
110
  | `taxonomy_list` | List topics, intents, and flags |
101
111
  | `taxonomy_counts` | Aggregate conversation counts by topic and intent |
112
+ | `analytics_report` | Read standard dashboard/report analytics views |
113
+ | `analytics_query` | Aggregate supported conversation dimensions with count |
114
+ | `metrics_query` | Roll up named metric events |
102
115
 
103
116
  ## Examples
104
117
 
@@ -42,8 +42,18 @@ applied knowledge-unprotect <id>
42
42
  # Taxonomy
43
43
  applied taxonomy --type topics
44
44
  applied taxonomy-counts --start 2026-03-01 --end 2026-03-18 --format csv
45
+
46
+ # Analytics
47
+ applied analytics-report --view overview_aggregate_metrics --model conversation --start 2026-04-01 --end 2026-04-30 --format json
48
+ applied analytics --group-by topic --metrics count --start 2026-04-01 --end 2026-04-30 --format json
49
+ applied analytics --group-by intent --metrics count --start 2026-04-01 --end 2026-04-30 --format json
50
+ applied metrics --metric-name conversation.resolve --start 2026-04-01 --end 2026-04-30 --period day --format json
45
51
  ```
46
52
 
53
+ `analytics-report` returns the selected report payload, not necessarily a `{ "rows": [...] }`
54
+ object. `analytics` returns grouped rows and currently supports `--metrics count`.
55
+ Raw analytics SQL is not available through the public CLI surface.
56
+
47
57
  ## Library Usage
48
58
 
49
59
  ```python
@@ -74,6 +84,9 @@ conversations = await tools.conversation_query(
74
84
  | `knowledge_list` | List knowledge base items |
75
85
  | `taxonomy_list` | List topics, intents, and flags |
76
86
  | `taxonomy_counts` | Aggregate conversation counts by topic and intent |
87
+ | `analytics_report` | Read standard dashboard/report analytics views |
88
+ | `analytics_query` | Aggregate supported conversation dimensions with count |
89
+ | `metrics_query` | Roll up named metric events |
77
90
 
78
91
  ## Examples
79
92
 
@@ -4,6 +4,6 @@ from applied_cli import tools
4
4
  from applied_cli.client import AppliedClient
5
5
  from applied_cli.formatters import to_csv, to_json
6
6
 
7
- __version__ = "0.5.72"
7
+ __version__ = "0.5.74"
8
8
 
9
9
  __all__ = ["AppliedClient", "tools", "to_csv", "to_json", "__version__"]
@@ -2,11 +2,94 @@
2
2
 
3
3
  import asyncio
4
4
  import json
5
+ import os
5
6
  import uuid
6
7
  from typing import Any
7
8
 
8
9
  import httpx
9
10
 
11
+ ACCESS_MODE_ENV_VAR = "APPLIED_ASSISTANT_ACCESS_MODE"
12
+ ASSISTANT_CONVERSATION_ENV_VAR = "APPLIED_ASSISTANT_CONVERSATION_ID"
13
+ FULL_ACCESS_MODE = "full_access"
14
+ DEFAULT_ACCESS_MODE = "default"
15
+ READ_ONLY_ACCESS_MODE = "read_only"
16
+ MUTATING_METHODS = {"POST", "PATCH", "DELETE", "PUT"}
17
+ OBJECT_COLLECTION_LABELS = {
18
+ "agents": "Agent",
19
+ "c": "Conversation",
20
+ "conversations": "Conversation",
21
+ "content": "Content",
22
+ "flows": "Flow",
23
+ "nodes": "Flow node",
24
+ "edges": "Flow edge",
25
+ "products": "Product",
26
+ "responses": "Response",
27
+ "tickets": "Ticket",
28
+ "tools": "Tool",
29
+ }
30
+
31
+
32
+ def _normalize_access_mode(access_mode: str | None) -> str:
33
+ if access_mode is None or not access_mode.strip():
34
+ return FULL_ACCESS_MODE
35
+
36
+ normalized = access_mode.strip().lower().replace("-", "_")
37
+ if normalized in {FULL_ACCESS_MODE, DEFAULT_ACCESS_MODE, READ_ONLY_ACCESS_MODE}:
38
+ return normalized
39
+ return DEFAULT_ACCESS_MODE
40
+
41
+
42
+ def _json_safe(value: Any) -> Any:
43
+ try:
44
+ return json.loads(json.dumps(value, default=str, ensure_ascii=True))
45
+ except (TypeError, ValueError):
46
+ return str(value)
47
+
48
+
49
+ def _infer_change_action(method: str) -> str:
50
+ method = method.upper()
51
+ if method == "POST":
52
+ return "add"
53
+ if method == "DELETE":
54
+ return "remove"
55
+ return "update"
56
+
57
+
58
+ def _infer_object_reference(path: str, body: Any) -> tuple[str, str, str]:
59
+ parts = [part for part in path.split("?", 1)[0].strip("/").split("/") if part]
60
+ if parts and parts[0].startswith("v") and len(parts) > 1:
61
+ parts = parts[1:]
62
+
63
+ object_type = "DB object"
64
+ object_id = ""
65
+ for index, part in enumerate(parts):
66
+ collection = part.replace("-", "_")
67
+ if collection not in OBJECT_COLLECTION_LABELS:
68
+ continue
69
+ object_type = OBJECT_COLLECTION_LABELS[collection]
70
+ if index + 1 < len(parts):
71
+ candidate = parts[index + 1]
72
+ if candidate.replace("-", "_") not in OBJECT_COLLECTION_LABELS:
73
+ object_id = candidate
74
+
75
+ object_name = ""
76
+ if isinstance(body, dict):
77
+ for key in ("name", "title", "label", "display_name", "displayName"):
78
+ value = body.get(key)
79
+ if isinstance(value, str) and value.strip():
80
+ object_name = value.strip()
81
+ break
82
+
83
+ return object_type, object_id, object_name
84
+
85
+
86
+ def _candidate_after(before: Any, body: Any, after: Any) -> Any:
87
+ if after is not None:
88
+ return after
89
+ if isinstance(before, dict) and isinstance(body, dict):
90
+ return {**before, **body}
91
+ return body or {}
92
+
10
93
 
11
94
  class AppliedAPIError(Exception):
12
95
  """API error with details and recovery suggestions."""
@@ -355,7 +438,10 @@ def _decode_dashboard_group(group: Any, expected_count: int) -> list[Any]:
355
438
 
356
439
  def _sort_analytics_rows(rows: list[dict[str, Any]], group_by: list[str]) -> None:
357
440
  def sort_key(row: dict[str, Any]) -> tuple[str, ...]:
358
- return tuple("" if row.get(dimension) is None else str(row.get(dimension)) for dimension in group_by)
441
+ return tuple(
442
+ "" if row.get(dimension) is None else str(row.get(dimension))
443
+ for dimension in group_by
444
+ )
359
445
 
360
446
  rows.sort(key=sort_key)
361
447
 
@@ -369,11 +455,163 @@ class AppliedClient:
369
455
  shop_id: str | None = None,
370
456
  base_url: str = "https://api.appliedlabs.ai",
371
457
  timeout: float = 30.0,
458
+ access_mode: str | None = None,
372
459
  ):
373
460
  self.token = token
374
461
  self.shop_id = shop_id
375
462
  self.base_url = base_url.rstrip("/")
376
463
  self.timeout = timeout
464
+ self.access_mode = _normalize_access_mode(
465
+ access_mode
466
+ if access_mode is not None
467
+ else os.environ.get(ACCESS_MODE_ENV_VAR)
468
+ )
469
+ self.assistant_conversation_id = os.environ.get(
470
+ ASSISTANT_CONVERSATION_ENV_VAR,
471
+ "",
472
+ ).strip()
473
+
474
+ async def _record_access_mode_change(
475
+ self,
476
+ *,
477
+ method: str,
478
+ path: str,
479
+ body: Any = None,
480
+ before: Any = None,
481
+ after: Any = None,
482
+ shop_id: str | None = None,
483
+ status: str,
484
+ ) -> None:
485
+ if not self.assistant_conversation_id or not self.token:
486
+ return
487
+
488
+ safe_body = _json_safe(body or {})
489
+ safe_before = _json_safe(before if before is not None else {})
490
+ safe_after = _json_safe(_candidate_after(before, body, after))
491
+ object_type, object_id, object_name = _infer_object_reference(
492
+ path,
493
+ safe_body if safe_body else safe_after,
494
+ )
495
+ resolved_shop_id = shop_id or self.shop_id
496
+ payload = {
497
+ "access_mode": self.access_mode,
498
+ "status": status,
499
+ "object_type": object_type,
500
+ "object_id": object_id,
501
+ "object_name": object_name,
502
+ "action": _infer_change_action(method),
503
+ "before": safe_before,
504
+ "after": safe_after,
505
+ "diff": {"request_body": safe_body},
506
+ "request_method": method.upper(),
507
+ "request_path": path,
508
+ "request_body": safe_body,
509
+ }
510
+ headers = {
511
+ "Authorization": f"Bearer {self.token}",
512
+ "Content-Type": "application/json",
513
+ }
514
+ if resolved_shop_id:
515
+ headers["X-Shop-Id"] = resolved_shop_id
516
+
517
+ timeout = (
518
+ min(self.timeout, 10.0) if isinstance(self.timeout, int | float) else 10.0
519
+ )
520
+ diffs_url = (
521
+ f"{self.base_url}/v1/threads/{self.assistant_conversation_id}/diffs/"
522
+ )
523
+ try:
524
+ async with httpx.AsyncClient(timeout=timeout) as client:
525
+ await client.post(
526
+ diffs_url,
527
+ headers=headers,
528
+ json=payload,
529
+ )
530
+ except Exception:
531
+ # Do not let proposal logging mask the access-mode decision.
532
+ return
533
+
534
+ async def _fetch_current_object(
535
+ self,
536
+ *,
537
+ method: str,
538
+ path: str,
539
+ shop_id: str | None = None,
540
+ ) -> Any:
541
+ if method.upper() not in {"PATCH", "PUT", "DELETE"}:
542
+ return {}
543
+
544
+ headers = {"Authorization": f"Bearer {self.token}"}
545
+ resolved_shop_id = shop_id or self.shop_id
546
+ if resolved_shop_id:
547
+ headers["X-Shop-Id"] = resolved_shop_id
548
+
549
+ timeout = (
550
+ min(self.timeout, 10.0) if isinstance(self.timeout, int | float) else 10.0
551
+ )
552
+ try:
553
+ async with httpx.AsyncClient(timeout=timeout) as client:
554
+ response = await client.get(f"{self.base_url}{path}", headers=headers)
555
+ if response.status_code >= 400:
556
+ return {}
557
+ return response.json()
558
+ except Exception:
559
+ return {}
560
+
561
+ async def _enforce_access_mode(
562
+ self,
563
+ method: str,
564
+ path: str,
565
+ *,
566
+ body: Any = None,
567
+ shop_id: str | None = None,
568
+ ) -> None:
569
+ method = method.upper()
570
+ if method not in MUTATING_METHODS or self.access_mode == FULL_ACCESS_MODE:
571
+ return
572
+
573
+ requires_approval = self.access_mode == DEFAULT_ACCESS_MODE
574
+ before = await self._fetch_current_object(
575
+ method=method,
576
+ path=path,
577
+ shop_id=shop_id,
578
+ )
579
+ await self._record_access_mode_change(
580
+ method=method,
581
+ path=path,
582
+ body=body,
583
+ before=before,
584
+ shop_id=shop_id,
585
+ status="proposed" if requires_approval else "blocked",
586
+ )
587
+ error_code = (
588
+ "requires_approval" if requires_approval else "read_only_access_mode"
589
+ )
590
+ message = (
591
+ f"{method} {path} was not sent because Applied Assistant access mode "
592
+ f"is {self.access_mode}."
593
+ )
594
+ detail = {
595
+ "error": error_code,
596
+ "requires_approval": requires_approval,
597
+ "access_mode": self.access_mode,
598
+ "method": method,
599
+ "path": path,
600
+ "message": message,
601
+ }
602
+ suggestion = (
603
+ "Request approval or run with APPLIED_ASSISTANT_ACCESS_MODE=full_access "
604
+ "before retrying this mutation."
605
+ if requires_approval
606
+ else "This environment is read-only. Re-run in full_access mode only when "
607
+ "mutations are intended."
608
+ )
609
+ raise AppliedAPIError(
610
+ message=f"{method} {path} blocked by access mode",
611
+ status_code=403,
612
+ detail=json.dumps(detail, separators=(",", ":"), ensure_ascii=True),
613
+ suggestion=suggestion,
614
+ )
377
615
 
378
616
  async def _request(
379
617
  self,
@@ -384,6 +622,13 @@ class AppliedClient:
384
622
  shop_id: str | None = None,
385
623
  ) -> Any:
386
624
  """Make an authenticated API request."""
625
+ method = method.upper()
626
+ await self._enforce_access_mode(
627
+ method,
628
+ path,
629
+ body=body,
630
+ shop_id=shop_id,
631
+ )
387
632
  headers = {
388
633
  "Authorization": f"Bearer {self.token}",
389
634
  "Content-Type": "application/json",
@@ -392,6 +637,13 @@ class AppliedClient:
392
637
  if resolved_shop_id:
393
638
  headers["X-Shop-Id"] = resolved_shop_id
394
639
  url = f"{self.base_url}{path}"
640
+ before = None
641
+ if method in MUTATING_METHODS and self.access_mode == FULL_ACCESS_MODE:
642
+ before = await self._fetch_current_object(
643
+ method=method,
644
+ path=path,
645
+ shop_id=resolved_shop_id,
646
+ )
395
647
 
396
648
  async with httpx.AsyncClient(timeout=self.timeout) as client:
397
649
  if method == "GET":
@@ -441,9 +693,19 @@ class AppliedClient:
441
693
  suggestion=suggestion,
442
694
  )
443
695
 
444
- if resp.status_code == 204:
445
- return None
446
- return resp.json()
696
+ result = None if resp.status_code == 204 else resp.json()
697
+
698
+ if method in MUTATING_METHODS and self.access_mode == FULL_ACCESS_MODE:
699
+ await self._record_access_mode_change(
700
+ method=method,
701
+ path=path,
702
+ body=body,
703
+ before=before,
704
+ after=result,
705
+ shop_id=shop_id,
706
+ status="applied",
707
+ )
708
+ return result
447
709
 
448
710
  async def _upload_request(
449
711
  self,
@@ -453,12 +715,21 @@ class AppliedClient:
453
715
  data: dict[str, Any] | None = None,
454
716
  ) -> Any:
455
717
  """Make an authenticated multipart file upload request."""
718
+ method = method.upper()
719
+ await self._enforce_access_mode(method, path, body=data, shop_id=self.shop_id)
456
720
  headers = {
457
721
  "Authorization": f"Bearer {self.token}",
458
722
  }
459
723
  if self.shop_id:
460
724
  headers["X-Shop-Id"] = self.shop_id
461
725
  url = f"{self.base_url}{path}"
726
+ before = None
727
+ if method in MUTATING_METHODS and self.access_mode == FULL_ACCESS_MODE:
728
+ before = await self._fetch_current_object(
729
+ method=method,
730
+ path=path,
731
+ shop_id=self.shop_id,
732
+ )
462
733
 
463
734
  async with httpx.AsyncClient(timeout=self.timeout) as client:
464
735
  resp = await client.request(
@@ -485,9 +756,19 @@ class AppliedClient:
485
756
  detail=detail,
486
757
  )
487
758
 
488
- if resp.status_code == 204:
489
- return None
490
- return resp.json()
759
+ result = None if resp.status_code == 204 else resp.json()
760
+
761
+ if method in MUTATING_METHODS and self.access_mode == FULL_ACCESS_MODE:
762
+ await self._record_access_mode_change(
763
+ method=method,
764
+ path=path,
765
+ body=data,
766
+ before=before,
767
+ after=result,
768
+ shop_id=self.shop_id,
769
+ status="applied",
770
+ )
771
+ return result
491
772
 
492
773
  def _require_shop_id(self, shop_id: str | None = None) -> str:
493
774
  resolved_shop_id = shop_id or self.shop_id
@@ -564,9 +845,7 @@ class AppliedClient:
564
845
  **kwargs: Any,
565
846
  ) -> dict:
566
847
  """Update an agent's properties."""
567
- return await self._request(
568
- "PATCH", f"/v1/agents/{agent_id}/", body=kwargs
569
- )
848
+ return await self._request("PATCH", f"/v1/agents/{agent_id}/", body=kwargs)
570
849
 
571
850
  async def update_agent_picture(
572
851
  self,
@@ -1886,6 +2165,18 @@ class AppliedClient:
1886
2165
  Returns:
1887
2166
  Dict with conversation_id, response, and status
1888
2167
  """
2168
+ path = f"/v1/agents/{agent_id}/complete/"
2169
+ await self._enforce_access_mode(
2170
+ "POST",
2171
+ path,
2172
+ body={
2173
+ "conversation_id": conversation_id or "",
2174
+ "message": message,
2175
+ "metadata": metadata or {},
2176
+ "flow_id": flow_id or "",
2177
+ },
2178
+ )
2179
+
1889
2180
  conversation = await self.ensure_test_conversation(
1890
2181
  agent_id=agent_id,
1891
2182
  conversation_id=conversation_id,
@@ -1922,7 +2213,7 @@ class AppliedClient:
1922
2213
  if runtime_metadata:
1923
2214
  body["metadata"] = runtime_metadata
1924
2215
 
1925
- url = f"{self.base_url}/v1/agents/{agent_id}/complete/"
2216
+ url = f"{self.base_url}{path}"
1926
2217
 
1927
2218
  response_text = ""
1928
2219
 
@@ -1944,11 +2235,20 @@ class AppliedClient:
1944
2235
  except json.JSONDecodeError:
1945
2236
  pass
1946
2237
 
1947
- return {
2238
+ result = {
1948
2239
  "conversation_id": result_conversation_id,
1949
2240
  "response": response_text,
1950
2241
  "status": "success" if response_text else "empty",
1951
2242
  }
2243
+ if self.access_mode == FULL_ACCESS_MODE:
2244
+ await self._record_access_mode_change(
2245
+ method="POST",
2246
+ path=path,
2247
+ body=body,
2248
+ after=result,
2249
+ status="applied",
2250
+ )
2251
+ return result
1952
2252
 
1953
2253
  # -------------------------------------------------------------------------
1954
2254
  # Benchmarks
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: applied-cli
3
- Version: 0.5.73
3
+ Version: 0.5.74
4
4
  Summary: CLI and shared client library for Applied Labs AI support agents
5
5
  Author: Applied Labs
6
6
  License-Expression: MIT
@@ -67,8 +67,18 @@ applied knowledge-unprotect <id>
67
67
  # Taxonomy
68
68
  applied taxonomy --type topics
69
69
  applied taxonomy-counts --start 2026-03-01 --end 2026-03-18 --format csv
70
+
71
+ # Analytics
72
+ applied analytics-report --view overview_aggregate_metrics --model conversation --start 2026-04-01 --end 2026-04-30 --format json
73
+ applied analytics --group-by topic --metrics count --start 2026-04-01 --end 2026-04-30 --format json
74
+ applied analytics --group-by intent --metrics count --start 2026-04-01 --end 2026-04-30 --format json
75
+ applied metrics --metric-name conversation.resolve --start 2026-04-01 --end 2026-04-30 --period day --format json
70
76
  ```
71
77
 
78
+ `analytics-report` returns the selected report payload, not necessarily a `{ "rows": [...] }`
79
+ object. `analytics` returns grouped rows and currently supports `--metrics count`.
80
+ Raw analytics SQL is not available through the public CLI surface.
81
+
72
82
  ## Library Usage
73
83
 
74
84
  ```python
@@ -99,6 +109,9 @@ conversations = await tools.conversation_query(
99
109
  | `knowledge_list` | List knowledge base items |
100
110
  | `taxonomy_list` | List topics, intents, and flags |
101
111
  | `taxonomy_counts` | Aggregate conversation counts by topic and intent |
112
+ | `analytics_report` | Read standard dashboard/report analytics views |
113
+ | `analytics_query` | Aggregate supported conversation dimensions with count |
114
+ | `metrics_query` | Roll up named metric events |
102
115
 
103
116
  ## Examples
104
117
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "applied-cli"
3
- version = "0.5.73"
3
+ version = "0.5.74"
4
4
  description = "CLI and shared client library for Applied Labs AI support agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
File without changes