applied-cli 0.5.70__tar.gz → 0.5.72__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 (29) hide show
  1. {applied_cli-0.5.70 → applied_cli-0.5.72}/PKG-INFO +11 -1
  2. {applied_cli-0.5.70 → applied_cli-0.5.72}/README.md +10 -0
  3. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli/__init__.py +1 -1
  4. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli/cli.py +287 -4
  5. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli/client.py +246 -20
  6. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli/tools.py +871 -18
  7. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli.egg-info/PKG-INFO +11 -1
  8. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli.egg-info/SOURCES.txt +2 -1
  9. {applied_cli-0.5.70 → applied_cli-0.5.72}/pyproject.toml +1 -1
  10. applied_cli-0.5.72/tests/test_cli.py +564 -0
  11. {applied_cli-0.5.70 → applied_cli-0.5.72}/tests/test_client.py +216 -40
  12. applied_cli-0.5.72/tests/test_knowledge_content_tools.py +383 -0
  13. applied_cli-0.5.70/tests/test_cli.py +0 -96
  14. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli/agent_scoped_flows.py +0 -0
  15. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli/conversation_lookup.py +0 -0
  16. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli/conversations.py +0 -0
  17. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli/credentials.py +0 -0
  18. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli/flow_helpers.py +0 -0
  19. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli/formatters.py +0 -0
  20. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli.egg-info/dependency_links.txt +0 -0
  21. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli.egg-info/entry_points.txt +0 -0
  22. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli.egg-info/requires.txt +0 -0
  23. {applied_cli-0.5.70 → applied_cli-0.5.72}/applied_cli.egg-info/top_level.txt +0 -0
  24. {applied_cli-0.5.70 → applied_cli-0.5.72}/setup.cfg +0 -0
  25. {applied_cli-0.5.70 → applied_cli-0.5.72}/tests/test_agent_scoped_flows.py +0 -0
  26. {applied_cli-0.5.70 → applied_cli-0.5.72}/tests/test_audit_tools.py +0 -0
  27. {applied_cli-0.5.70 → applied_cli-0.5.72}/tests/test_benchmark_scenario_tools.py +0 -0
  28. {applied_cli-0.5.70 → applied_cli-0.5.72}/tests/test_conversation_tools.py +0 -0
  29. {applied_cli-0.5.70 → applied_cli-0.5.72}/tests/test_flow_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: applied-cli
3
- Version: 0.5.70
3
+ Version: 0.5.72
4
4
  Summary: CLI and shared client library for Applied Labs AI support agents
5
5
  Author: Applied Labs
6
6
  License-Expression: MIT
@@ -53,6 +53,16 @@ applied tickets --status open
53
53
 
54
54
  # Knowledge base
55
55
  applied knowledge --type qa --search "refund"
56
+ applied knowledge <id> --format json
57
+ applied knowledge --source "https://help.example.com" --format csv
58
+ applied knowledge-diff --source "https://help.example.com"
59
+ applied content list --source "https://help.example.com"
60
+ applied content-update <content_id> --text "Corrected source text"
61
+ applied content-resync <content_id> --wait
62
+ applied knowledge-pin <id>
63
+ applied knowledge-link <response_id> --content <content_id>
64
+ applied knowledge-protect <id>
65
+ applied knowledge-unprotect <id>
56
66
 
57
67
  # Taxonomy
58
68
  applied taxonomy --type topics
@@ -28,6 +28,16 @@ applied tickets --status open
28
28
 
29
29
  # Knowledge base
30
30
  applied knowledge --type qa --search "refund"
31
+ applied knowledge <id> --format json
32
+ applied knowledge --source "https://help.example.com" --format csv
33
+ applied knowledge-diff --source "https://help.example.com"
34
+ applied content list --source "https://help.example.com"
35
+ applied content-update <content_id> --text "Corrected source text"
36
+ applied content-resync <content_id> --wait
37
+ applied knowledge-pin <id>
38
+ applied knowledge-link <response_id> --content <content_id>
39
+ applied knowledge-protect <id>
40
+ applied knowledge-unprotect <id>
31
41
 
32
42
  # Taxonomy
33
43
  applied taxonomy --type topics
@@ -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.70"
7
+ __version__ = "0.5.71"
8
8
 
9
9
  __all__ = ["AppliedClient", "tools", "to_csv", "to_json", "__version__"]
@@ -22,6 +22,8 @@ app = typer.Typer(
22
22
  help="Applied Labs CLI - manage your AI agents and conversations.",
23
23
  no_args_is_help=True,
24
24
  )
25
+ content_app = typer.Typer(help="Inspect synced content items.")
26
+ app.add_typer(content_app, name="content")
25
27
 
26
28
  DEFAULT_BASE_URL = "https://api.appliedlabs.ai"
27
29
  DEFAULT_CLIENT_URL = "https://appliedlabs.ai"
@@ -289,7 +291,7 @@ def analytics(
289
291
  "--group-by",
290
292
  "-g",
291
293
  help=(
292
- "Comma-separated dimensions: label_name, sublabel_name, agent_name, "
294
+ "Comma-separated dimensions: topic, intent, label_name, sublabel_name, "
293
295
  "resolution, sentiment, type, user_id, label_id, sublabel_id, agent_id"
294
296
  ),
295
297
  ),
@@ -297,7 +299,7 @@ def analytics(
297
299
  "count",
298
300
  "--metrics",
299
301
  "-m",
300
- help="Comma-separated metrics: count, avg_score, avg_ttr, resolution_rate",
302
+ help="Comma-separated metrics: count",
301
303
  ),
302
304
  filter: list[str] | None = typer.Option( # noqa: B008
303
305
  None,
@@ -938,27 +940,174 @@ def ticket_update(
938
940
 
939
941
  @app.command()
940
942
  def knowledge(
943
+ id: str = typer.Argument(None, help="Knowledge item ID to fetch"),
941
944
  type: str = typer.Option(
942
945
  None, "--type", "-t", help="Filter by type: qa, context, escalate, exact"
943
946
  ),
944
947
  search: str = typer.Option(None, "--search", "-s", help="Search query"),
948
+ linked_to: str = typer.Option(
949
+ None, "--linked-to", help="Filter by linked content ID"
950
+ ),
951
+ source: str = typer.Option(
952
+ None, "--source", help="Filter by linked content source URL/domain"
953
+ ),
954
+ has_content: bool | None = typer.Option(
955
+ None,
956
+ "--has-content/--no-content",
957
+ help="Filter content-linked vs standalone manual entries",
958
+ ),
945
959
  limit: int = typer.Option(100, "--limit", "-l", help="Results per page"),
946
960
  all_pages: bool = typer.Option(
947
961
  True, "--all/--no-all", help="Fetch all pages (default: True)"
948
962
  ),
963
+ full: bool = typer.Option(False, "--full", help="Include full audit fields in CSV"),
949
964
  format: str = typer.Option(
950
965
  "csv", "--format", "-f", help="Output format: csv or json"
951
966
  ),
967
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
952
968
  ) -> None:
953
- """List knowledge base items."""
954
- client = get_client()
969
+ """List knowledge base items or fetch one item by ID."""
970
+ client = get_client(shop_id=shop_id)
971
+ if id:
972
+ item_format = "json" if format == "json" else "text"
973
+ result = asyncio.run(tools.knowledge_get(client, id, output_format=item_format))
974
+ typer.echo(result)
975
+ return
976
+
955
977
  result = asyncio.run(
956
978
  tools.knowledge_list(
957
979
  client,
958
980
  kb_type=type,
959
981
  search=search,
982
+ linked_to=linked_to,
983
+ source=source,
984
+ has_content=has_content,
960
985
  limit=limit,
961
986
  fetch_all=all_pages,
987
+ full=full,
988
+ output_format=format,
989
+ )
990
+ )
991
+ typer.echo(result)
992
+
993
+
994
+ @app.command("knowledge-diff")
995
+ def knowledge_diff(
996
+ source: str = typer.Option(..., "--source", help="Source URL/domain to audit"),
997
+ limit: int = typer.Option(100, "--limit", "-l", help="Results per API page"),
998
+ format: str = typer.Option(
999
+ "text", "--format", "-f", help="Output format: text or json"
1000
+ ),
1001
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
1002
+ ) -> None:
1003
+ """Diff content-linked knowledge against current source pages."""
1004
+ client = get_client(shop_id=shop_id)
1005
+ result = asyncio.run(
1006
+ tools.knowledge_diff(
1007
+ client,
1008
+ source=source,
1009
+ limit=limit,
1010
+ output_format=format,
1011
+ )
1012
+ )
1013
+ typer.echo(result)
1014
+
1015
+
1016
+ # -----------------------------------------------------------------------------
1017
+ # Content commands
1018
+ # -----------------------------------------------------------------------------
1019
+
1020
+
1021
+ @content_app.command("list")
1022
+ def content_list_cmd(
1023
+ type: str = typer.Option(
1024
+ None, "--type", "-t", help="Filter by type: Article, Product, Document, Site"
1025
+ ),
1026
+ status: str = typer.Option(None, "--status", "-s", help="Filter by status"),
1027
+ search: str = typer.Option(None, "--search", help="Search query"),
1028
+ source: str = typer.Option(None, "--source", help="Filter by source URL/domain"),
1029
+ limit: int = typer.Option(100, "--limit", "-l", help="Results per page"),
1030
+ all_pages: bool = typer.Option(
1031
+ True, "--all/--no-all", help="Fetch all pages (default: True)"
1032
+ ),
1033
+ full: bool = typer.Option(False, "--full", help="Include sync audit fields in CSV"),
1034
+ format: str = typer.Option(
1035
+ "csv", "--format", "-f", help="Output format: csv or json"
1036
+ ),
1037
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
1038
+ ) -> None:
1039
+ """List content items imported into the knowledge system."""
1040
+ client = get_client(shop_id=shop_id)
1041
+ result = asyncio.run(
1042
+ tools.content_list(
1043
+ client,
1044
+ content_type=type,
1045
+ status=status,
1046
+ search=search,
1047
+ source=source,
1048
+ limit=limit,
1049
+ fetch_all=all_pages,
1050
+ full=full,
1051
+ output_format=format,
1052
+ )
1053
+ )
1054
+ typer.echo(result)
1055
+
1056
+
1057
+ @app.command("content-resync")
1058
+ def content_resync(
1059
+ id: str = typer.Argument(..., help="Content item ID"),
1060
+ wait: bool = typer.Option(
1061
+ False, "--wait", help="Poll until the content item is no longer syncing"
1062
+ ),
1063
+ poll_interval: float = typer.Option(
1064
+ 2.0, "--poll-interval", help="Seconds between --wait polls"
1065
+ ),
1066
+ timeout: float = typer.Option(
1067
+ 300.0, "--timeout", help="Maximum seconds to wait for sync completion"
1068
+ ),
1069
+ format: str = typer.Option(
1070
+ "text", "--format", "-f", help="Output format: text or json"
1071
+ ),
1072
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
1073
+ ) -> None:
1074
+ """Trigger a resync for one content item."""
1075
+ client = get_client(shop_id=shop_id)
1076
+ result = asyncio.run(
1077
+ tools.content_resync(
1078
+ client,
1079
+ id,
1080
+ wait=wait,
1081
+ poll_interval=poll_interval,
1082
+ timeout=timeout,
1083
+ output_format=format,
1084
+ )
1085
+ )
1086
+ typer.echo(result)
1087
+
1088
+
1089
+ @app.command("content-update")
1090
+ def content_update(
1091
+ id: str = typer.Argument(..., help="Content item ID"),
1092
+ text: str = typer.Option(None, "--text", help="Override source text"),
1093
+ title: str = typer.Option(None, "--title", help="Content title"),
1094
+ description: str = typer.Option(None, "--description", help="Content description"),
1095
+ status: str = typer.Option(None, "--status", help="Content status"),
1096
+ format: str = typer.Option(
1097
+ "text", "--format", "-f", help="Output format: text or json"
1098
+ ),
1099
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
1100
+ ) -> None:
1101
+ """Update a content item before resyncing linked responses."""
1102
+ client = get_client(shop_id=shop_id)
1103
+ result = asyncio.run(
1104
+ tools.content_update(
1105
+ client,
1106
+ id,
1107
+ text=text,
1108
+ title=title,
1109
+ description=description,
1110
+ status=status,
962
1111
  output_format=format,
963
1112
  )
964
1113
  )
@@ -1923,6 +2072,9 @@ def knowledge_create(
1923
2072
  ),
1924
2073
  question: str = typer.Option(..., "--question", "-q", help="Trigger text / question"),
1925
2074
  answer: str = typer.Option(..., "--answer", "-a", help="Response text / answer"),
2075
+ chunk_text: str = typer.Option(
2076
+ None, "--chunk-text", help="Raw retrieval chunk text"
2077
+ ),
1926
2078
  guardrail: str = typer.Option("", "--guardrail", help="Guardrail instructions"),
1927
2079
  active: bool = typer.Option(True, "--active/--inactive", help="Whether the item is active"),
1928
2080
  agent_ids: str = typer.Option(
@@ -1941,6 +2093,7 @@ def knowledge_create(
1941
2093
  kb_type=kb_type,
1942
2094
  question=question,
1943
2095
  answer=answer,
2096
+ chunk_text=chunk_text,
1944
2097
  guardrail=guardrail,
1945
2098
  active=active,
1946
2099
  agent_ids=parsed_agent_ids,
@@ -1963,6 +2116,11 @@ def knowledge_update(
1963
2116
  ),
1964
2117
  label_id: str = typer.Option(None, "--label-id", help="Topic label UUID"),
1965
2118
  flow_id: str = typer.Option(None, "--flow-id", help="Flow UUID to associate with"),
2119
+ pin: bool = typer.Option(
2120
+ False,
2121
+ "--pin",
2122
+ help="Mark the response as manually managed while updating",
2123
+ ),
1966
2124
  shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
1967
2125
  ) -> None:
1968
2126
  """Update a knowledge base item."""
@@ -1982,6 +2140,95 @@ def knowledge_update(
1982
2140
  agent_ids=parsed_agent_ids,
1983
2141
  label_id=label_id,
1984
2142
  flow_id=flow_id,
2143
+ pin=pin,
2144
+ )
2145
+ )
2146
+ typer.echo(result)
2147
+
2148
+
2149
+ @app.command("knowledge-pin")
2150
+ def knowledge_pin(
2151
+ id: str = typer.Argument(..., help="Knowledge item ID"),
2152
+ unpin: bool = typer.Option(False, "--unpin", help="Remove the pin/manual flag"),
2153
+ format: str = typer.Option(
2154
+ "text", "--format", "-f", help="Output format: text or json"
2155
+ ),
2156
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
2157
+ ) -> None:
2158
+ """Pin a knowledge item so manual edits can survive content resyncs."""
2159
+ client = get_client(shop_id=shop_id)
2160
+ result = asyncio.run(
2161
+ tools.knowledge_pin(
2162
+ client,
2163
+ id,
2164
+ pinned=not unpin,
2165
+ output_format=format,
2166
+ )
2167
+ )
2168
+ typer.echo(result)
2169
+
2170
+
2171
+ @app.command("knowledge-link")
2172
+ def knowledge_link(
2173
+ id: str = typer.Argument(..., help="Knowledge item ID"),
2174
+ content_id: str = typer.Option(
2175
+ ..., "--content", help="Content item ID to link to"
2176
+ ),
2177
+ format: str = typer.Option(
2178
+ "text", "--format", "-f", help="Output format: text or json"
2179
+ ),
2180
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
2181
+ ) -> None:
2182
+ """Link a knowledge item to a content item so sync can manage it."""
2183
+ client = get_client(shop_id=shop_id)
2184
+ result = asyncio.run(
2185
+ tools.knowledge_link(
2186
+ client,
2187
+ id,
2188
+ content_id=content_id,
2189
+ output_format=format,
2190
+ )
2191
+ )
2192
+ typer.echo(result)
2193
+
2194
+
2195
+ @app.command("knowledge-protect")
2196
+ def knowledge_protect(
2197
+ id: str = typer.Argument(..., help="Knowledge item ID"),
2198
+ format: str = typer.Option(
2199
+ "text", "--format", "-f", help="Output format: text or json"
2200
+ ),
2201
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
2202
+ ) -> None:
2203
+ """Disable autogeneration on the linked content for a knowledge item."""
2204
+ client = get_client(shop_id=shop_id)
2205
+ result = asyncio.run(
2206
+ tools.knowledge_protect(
2207
+ client,
2208
+ id,
2209
+ protected=True,
2210
+ output_format=format,
2211
+ )
2212
+ )
2213
+ typer.echo(result)
2214
+
2215
+
2216
+ @app.command("knowledge-unprotect")
2217
+ def knowledge_unprotect(
2218
+ id: str = typer.Argument(..., help="Knowledge item ID"),
2219
+ format: str = typer.Option(
2220
+ "text", "--format", "-f", help="Output format: text or json"
2221
+ ),
2222
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
2223
+ ) -> None:
2224
+ """Re-enable autogeneration on the linked content for a knowledge item."""
2225
+ client = get_client(shop_id=shop_id)
2226
+ result = asyncio.run(
2227
+ tools.knowledge_protect(
2228
+ client,
2229
+ id,
2230
+ protected=False,
2231
+ output_format=format,
1985
2232
  )
1986
2233
  )
1987
2234
  typer.echo(result)
@@ -2226,6 +2473,42 @@ def product_update(
2226
2473
  typer.echo(result)
2227
2474
 
2228
2475
 
2476
+ @app.command("agent-deploy")
2477
+ def agent_deploy(
2478
+ agent_ids: list[str] = typer.Argument(..., help="One or more Agent IDs to deploy"),
2479
+ description: str = typer.Option("", "--description", "-d", help="Optional revision description"),
2480
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
2481
+ ) -> None:
2482
+ """Deploy the current configuration for one or more agents as new live revisions.
2483
+
2484
+ Creates a new revision from each agent's current state and immediately sets it live,
2485
+ so new conversations use the latest changes.
2486
+
2487
+ Pass multiple agent IDs to deploy several agents at once:
2488
+ applied agent-deploy <id1> <id2> <id3> --description "what changed"
2489
+ """
2490
+ client = get_client(shop_id=shop_id)
2491
+
2492
+ async def _deploy_one(agent_id: str):
2493
+ return await client._request(
2494
+ "POST",
2495
+ "/v1/revisions/",
2496
+ body={"agent_id": agent_id, "deploy": True, "description": description},
2497
+ )
2498
+
2499
+ async def _deploy_all():
2500
+ import asyncio as _asyncio
2501
+ return await _asyncio.gather(*[_deploy_one(aid) for aid in agent_ids])
2502
+
2503
+ results = asyncio.run(_deploy_all())
2504
+ for result in results:
2505
+ version = result.get("version", "?")
2506
+ revision_id = result.get("id", "?")
2507
+ is_live = result.get("is_live", False)
2508
+ agent_id = result.get("agent_id", "?")
2509
+ typer.echo(f"Deployed agent {agent_id}: revision v{version} (id={revision_id}, is_live={is_live})")
2510
+
2511
+
2229
2512
  def main() -> None:
2230
2513
  """CLI entrypoint."""
2231
2514
  nested_exit_code = run_agent_scoped_flow_command(sys.argv[1:], get_client)