applied-cli 0.5.53__tar.gz → 0.5.55__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 (27) hide show
  1. {applied_cli-0.5.53 → applied_cli-0.5.55}/PKG-INFO +1 -1
  2. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli/cli.py +17 -0
  3. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli/client.py +21 -0
  4. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli/flow_helpers.py +3 -0
  5. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli/tools.py +151 -7
  6. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli.egg-info/PKG-INFO +1 -1
  7. {applied_cli-0.5.53 → applied_cli-0.5.55}/pyproject.toml +1 -1
  8. {applied_cli-0.5.53 → applied_cli-0.5.55}/README.md +0 -0
  9. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli/__init__.py +0 -0
  10. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli/agent_scoped_flows.py +0 -0
  11. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli/conversation_lookup.py +0 -0
  12. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli/conversations.py +0 -0
  13. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli/credentials.py +0 -0
  14. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli/formatters.py +0 -0
  15. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli.egg-info/SOURCES.txt +0 -0
  16. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli.egg-info/dependency_links.txt +0 -0
  17. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli.egg-info/entry_points.txt +0 -0
  18. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli.egg-info/requires.txt +0 -0
  19. {applied_cli-0.5.53 → applied_cli-0.5.55}/applied_cli.egg-info/top_level.txt +0 -0
  20. {applied_cli-0.5.53 → applied_cli-0.5.55}/setup.cfg +0 -0
  21. {applied_cli-0.5.53 → applied_cli-0.5.55}/tests/test_agent_scoped_flows.py +0 -0
  22. {applied_cli-0.5.53 → applied_cli-0.5.55}/tests/test_audit_tools.py +0 -0
  23. {applied_cli-0.5.53 → applied_cli-0.5.55}/tests/test_benchmark_scenario_tools.py +0 -0
  24. {applied_cli-0.5.53 → applied_cli-0.5.55}/tests/test_cli.py +0 -0
  25. {applied_cli-0.5.53 → applied_cli-0.5.55}/tests/test_client.py +0 -0
  26. {applied_cli-0.5.53 → applied_cli-0.5.55}/tests/test_conversation_tools.py +0 -0
  27. {applied_cli-0.5.53 → applied_cli-0.5.55}/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.53
3
+ Version: 0.5.55
4
4
  Summary: CLI and shared client library for Applied Labs AI support agents
5
5
  Author: Applied Labs
6
6
  License-Expression: MIT
@@ -1836,6 +1836,23 @@ def audit_rating_set_value(
1836
1836
  typer.echo(result)
1837
1837
 
1838
1838
 
1839
+ @app.command("connector-types")
1840
+ def connector_types(
1841
+ type: str = typer.Option(
1842
+ None, "--type", "-t", help="Filter to specific connector (e.g. shopify, zendesk)"
1843
+ ),
1844
+ format: str = typer.Option(
1845
+ "text", "--format", "-f", help="Output format: text, csv, or json"
1846
+ ),
1847
+ ) -> None:
1848
+ """List available connector types and their supported actions."""
1849
+ client = get_client()
1850
+ result = asyncio.run(
1851
+ tools.connector_types(client, connector_type=type, output_format=format)
1852
+ )
1853
+ typer.echo(result)
1854
+
1855
+
1839
1856
  def main() -> None:
1840
1857
  """CLI entrypoint."""
1841
1858
  nested_exit_code = run_agent_scoped_flow_command(sys.argv[1:], get_client)
@@ -431,6 +431,24 @@ class AppliedClient:
431
431
  )
432
432
  return self._normalize_response(data)
433
433
 
434
+ # -------------------------------------------------------------------------
435
+ # Connector Types
436
+ # -------------------------------------------------------------------------
437
+
438
+ async def list_connector_types(
439
+ self,
440
+ connector_type: str | None = None,
441
+ include_schemas: bool = False,
442
+ ) -> list[dict]:
443
+ """List available connector types and their supported actions."""
444
+ params: dict[str, Any] = {}
445
+ if connector_type:
446
+ params["type"] = connector_type
447
+ if include_schemas:
448
+ params["include_schemas"] = "true"
449
+ data = await self._request("GET", "/v2/connectors/types/", params=params)
450
+ return data if isinstance(data, list) else data.get("results", [data])
451
+
434
452
  # -------------------------------------------------------------------------
435
453
  # Taxonomy (topics, intents, flags)
436
454
  # -------------------------------------------------------------------------
@@ -1156,6 +1174,7 @@ class AppliedClient:
1156
1174
  source_handle: str | None = None,
1157
1175
  target_handle: str | None = None,
1158
1176
  label: str | None = None,
1177
+ metadata: dict[str, Any] | None = None,
1159
1178
  ) -> dict:
1160
1179
  """Create an edge connecting two nodes."""
1161
1180
  body: dict[str, Any] = {
@@ -1168,6 +1187,8 @@ class AppliedClient:
1168
1187
  body["target_handle"] = target_handle
1169
1188
  if label:
1170
1189
  body["label"] = label
1190
+ if metadata:
1191
+ body["metadata"] = metadata
1171
1192
 
1172
1193
  return await self._request(
1173
1194
  "POST",
@@ -28,6 +28,7 @@ _CANONICAL_EXECUTOR_TYPES: tuple[str, ...] = (
28
28
  "mutate_ticket",
29
29
  "mutate_conversation",
30
30
  "mutate_message",
31
+ "end_conversation",
31
32
  )
32
33
 
33
34
  _EXECUTOR_UI_LABELS: dict[str, str] = {
@@ -49,6 +50,7 @@ _EXECUTOR_UI_LABELS: dict[str, str] = {
49
50
  "mutate_ticket": "Ticket",
50
51
  "mutate_conversation": "Conversation",
51
52
  "mutate_message": "Add Message",
53
+ "end_conversation": "End Conversation",
52
54
  }
53
55
 
54
56
  _EXECUTOR_ALIASES: dict[str, tuple[str, ...]] = {
@@ -70,6 +72,7 @@ _EXECUTOR_ALIASES: dict[str, tuple[str, ...]] = {
70
72
  "mutate_ticket": ("ticket",),
71
73
  "mutate_conversation": ("conversation",),
72
74
  "mutate_message": ("add message",),
75
+ "end_conversation": ("end conversation", "end call", "close conversation"),
73
76
  }
74
77
 
75
78
  _BRANCH_DEFAULT_ALIASES = {
@@ -2500,6 +2500,9 @@ async def flow_edge_create(
2500
2500
  branch: str | int | None = None,
2501
2501
  target_handle: str | None = None,
2502
2502
  label: str | None = None,
2503
+ metadata: dict | None = None,
2504
+ temporal_timeout_seconds: int | None = None,
2505
+ temporal_disabled_contact_groups: dict | None = None,
2503
2506
  ) -> str:
2504
2507
  """
2505
2508
  Connect two nodes with an edge.
@@ -2513,7 +2516,14 @@ async def flow_edge_create(
2513
2516
  branch: Branch path alias for conversational branch nodes, e.g. "1",
2514
2517
  "branch 2", or "default". Preferred over source_handle for branch nodes.
2515
2518
  target_handle: Input handle name (for mapping)
2516
- label: Edge label/condition
2519
+ label: Edge label/condition describing when to traverse this edge
2520
+ metadata: Edge metadata dict. For temporal edges, include a "temporal"
2521
+ key (or use the temporal_* convenience params below).
2522
+ temporal_timeout_seconds: Convenience param to create a temporal edge
2523
+ that auto-fires after this many seconds of inactivity.
2524
+ temporal_disabled_contact_groups: Optional dict of contact group
2525
+ overrides for the temporal edge, e.g.
2526
+ {"__default__": false, "<group_id>": true}.
2517
2527
 
2518
2528
  Returns:
2519
2529
  Created edge
@@ -2523,6 +2533,14 @@ async def flow_edge_create(
2523
2533
 
2524
2534
  resolved_source_handle = normalize_branch_handle(branch or source_handle)
2525
2535
 
2536
+ # Build metadata with temporal settings
2537
+ metadata = dict(metadata or {})
2538
+ if temporal_timeout_seconds is not None:
2539
+ temporal = metadata.setdefault("temporal", {})
2540
+ temporal["timeout_seconds"] = temporal_timeout_seconds
2541
+ if temporal_disabled_contact_groups is not None:
2542
+ temporal["disabled_contact_groups"] = temporal_disabled_contact_groups
2543
+
2526
2544
  try:
2527
2545
  flow = await client.get_flow(flow_id)
2528
2546
  except AppliedAPIError as e:
@@ -2589,6 +2607,7 @@ async def flow_edge_create(
2589
2607
  source_handle=resolved_source_handle,
2590
2608
  target_handle=target_handle,
2591
2609
  label=label,
2610
+ metadata=metadata or None,
2592
2611
  )
2593
2612
  except AppliedAPIError as e:
2594
2613
  # Add extra context for edge errors
@@ -2602,14 +2621,33 @@ async def flow_edge_create(
2602
2621
  )
2603
2622
  return _format_error(e) + extra
2604
2623
 
2624
+ edge_id = edge.get("id")
2625
+
2626
+ # Follow-up update to persist label and metadata (backend create
2627
+ # endpoint may not pass these through to the model).
2628
+ needs_update = label or metadata
2629
+ if needs_update and edge_id:
2630
+ updates: dict = {}
2631
+ if label:
2632
+ updates["label"] = label
2633
+ if metadata:
2634
+ updates["metadata"] = metadata
2635
+ try:
2636
+ edge = await client.update_edge(flow_id, edge_id, **updates)
2637
+ except AppliedAPIError:
2638
+ pass # best-effort; edge was already created
2639
+
2605
2640
  result = "# Created Edge\n"
2606
- result += f"id: {edge.get('id')}\n"
2641
+ result += f"id: {edge_id}\n"
2607
2642
  result += f"source: {edge.get('source')}\n"
2608
2643
  result += f"target: {edge.get('target')}\n"
2609
2644
  if resolved_source_handle is not None:
2610
2645
  result += f"source_handle: {resolved_source_handle}\n"
2611
2646
  if label:
2612
2647
  result += f"label: {label}\n"
2648
+ if metadata.get("temporal"):
2649
+ temporal = metadata["temporal"]
2650
+ result += f"temporal_timeout_seconds: {temporal.get('timeout_seconds')}\n"
2613
2651
  result += "\nTip: run flow_layout(flow_id) after wiring a complex flow to tidy node positions.\n"
2614
2652
 
2615
2653
  return result
@@ -2666,6 +2704,8 @@ async def flow_edge_update(
2666
2704
  edge_id: str,
2667
2705
  label: str | None = None,
2668
2706
  metadata: dict | None = None,
2707
+ temporal_timeout_seconds: int | None = None,
2708
+ temporal_disabled_contact_groups: dict | None = None,
2669
2709
  ) -> str:
2670
2710
  """
2671
2711
  Update an edge's properties.
@@ -2674,21 +2714,39 @@ async def flow_edge_update(
2674
2714
  client: Authenticated AppliedClient
2675
2715
  flow_id: The flow UUID
2676
2716
  edge_id: The edge UUID
2677
- label: New label/condition
2678
- metadata: New metadata
2717
+ label: New label/condition describing when to traverse this edge
2718
+ metadata: New metadata dict. For temporal edges, include a "temporal"
2719
+ key or use the temporal_* convenience params.
2720
+ temporal_timeout_seconds: Set/update the temporal timeout in seconds.
2721
+ Makes this edge auto-fire after inactivity.
2722
+ temporal_disabled_contact_groups: Contact group overrides for the
2723
+ temporal edge, e.g. {"__default__": false, "<group_id>": true}.
2679
2724
 
2680
2725
  Returns:
2681
2726
  Updated edge
2682
2727
  """
2683
- updates = {}
2728
+ metadata = dict(metadata or {}) if metadata else {}
2729
+ if temporal_timeout_seconds is not None:
2730
+ temporal = metadata.setdefault("temporal", {})
2731
+ temporal["timeout_seconds"] = temporal_timeout_seconds
2732
+ if temporal_disabled_contact_groups is not None:
2733
+ temporal["disabled_contact_groups"] = temporal_disabled_contact_groups
2734
+
2735
+ updates: dict = {}
2684
2736
  if label is not None:
2685
2737
  updates["label"] = label
2686
- if metadata is not None:
2738
+ if metadata:
2687
2739
  updates["metadata"] = metadata
2688
2740
 
2689
2741
  edge = await client.update_edge(flow_id, edge_id, **updates)
2690
2742
 
2691
- return f"# Updated Edge\nid: {edge.get('id')}"
2743
+ result = f"# Updated Edge\nid: {edge.get('id')}"
2744
+ if label is not None:
2745
+ result += f"\nlabel: {label}"
2746
+ if metadata.get("temporal"):
2747
+ temporal = metadata["temporal"]
2748
+ result += f"\ntemporal_timeout_seconds: {temporal.get('timeout_seconds')}"
2749
+ return result
2692
2750
 
2693
2751
 
2694
2752
  async def flow_edge_delete(
@@ -3518,6 +3576,92 @@ async def send_message(
3518
3576
  return f"Error sending message: {e}"
3519
3577
 
3520
3578
 
3579
+ async def connector_types(
3580
+ client: AppliedClient,
3581
+ connector_type: str | None = None,
3582
+ output_format: str = "text",
3583
+ ) -> str:
3584
+ """
3585
+ List available connector types and their supported actions.
3586
+
3587
+ Args:
3588
+ client: Authenticated AppliedClient
3589
+ connector_type: Optional filter to a specific connector (e.g. 'shopify')
3590
+ output_format: 'text' (default), 'csv' (summary), 'json' (raw)
3591
+
3592
+ Returns:
3593
+ Connector types with their available actions and metadata
3594
+ """
3595
+ try:
3596
+ data = await client.list_connector_types(
3597
+ connector_type=connector_type,
3598
+ include_schemas=(output_format == "json"),
3599
+ )
3600
+ except Exception as e:
3601
+ return f"Error listing connector types: {e}"
3602
+
3603
+ if output_format == "json":
3604
+ return to_json(data)
3605
+
3606
+ if output_format == "csv":
3607
+ rows = []
3608
+ for connector in data:
3609
+ for action in connector.get("actions", []):
3610
+ rows.append(
3611
+ {
3612
+ "connector": connector["name"],
3613
+ "action": action["name"],
3614
+ "description": action.get("description", ""),
3615
+ "parameters": ", ".join(
3616
+ p["name"] for p in action.get("parameters", [])
3617
+ ),
3618
+ }
3619
+ )
3620
+ return to_csv(rows, ["connector", "action", "description", "parameters"])
3621
+
3622
+ # Default: text output
3623
+ if not data:
3624
+ return "No connector types found."
3625
+
3626
+ result = "# Available Connector Types\n\n"
3627
+ result += (
3628
+ "Use with: flow_node_create(executor_type='resource', "
3629
+ "metadata={'resource_type': '<connector>', 'action': '<action>', "
3630
+ "'resource_id': '<uuid>'})\n\n"
3631
+ )
3632
+
3633
+ for connector in data:
3634
+ name = connector["name"]
3635
+ action_count = connector.get("action_count", len(connector.get("actions", [])))
3636
+ result += f"## {name} ({action_count} actions)\n\n"
3637
+
3638
+ for action in connector.get("actions", []):
3639
+ result += f" {action['name']}"
3640
+ desc = action.get("description", "")
3641
+ if desc:
3642
+ # Show first line of description
3643
+ first_line = desc.split("\n")[0].strip()
3644
+ result += f" — {first_line}"
3645
+ result += "\n"
3646
+
3647
+ params = action.get("parameters", [])
3648
+ if params:
3649
+ for p in params:
3650
+ req = " (required)" if p.get("required") else ""
3651
+ pline = f" {p['name']}: {p.get('type', 'any')}{req}"
3652
+ if p.get("description"):
3653
+ pline += f" — {p['description']}"
3654
+ result += pline + "\n"
3655
+
3656
+ ret = action.get("return_type")
3657
+ if ret:
3658
+ result += f" → {ret}\n"
3659
+
3660
+ result += "\n"
3661
+
3662
+ return result
3663
+
3664
+
3521
3665
  async def executor_list(
3522
3666
  client: AppliedClient,
3523
3667
  output_format: str = "full",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: applied-cli
3
- Version: 0.5.53
3
+ Version: 0.5.55
4
4
  Summary: CLI and shared client library for Applied Labs AI support agents
5
5
  Author: Applied Labs
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "applied-cli"
3
- version = "0.5.53"
3
+ version = "0.5.55"
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
File without changes