applied-cli 0.5.72__tar.gz → 0.5.73__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.72 → applied_cli-0.5.73}/PKG-INFO +1 -1
  2. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli/__init__.py +1 -1
  3. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli/cli.py +245 -0
  4. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli.egg-info/PKG-INFO +1 -1
  5. {applied_cli-0.5.72 → applied_cli-0.5.73}/pyproject.toml +1 -1
  6. {applied_cli-0.5.72 → applied_cli-0.5.73}/README.md +0 -0
  7. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli/agent_scoped_flows.py +0 -0
  8. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli/client.py +0 -0
  9. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli/conversation_lookup.py +0 -0
  10. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli/conversations.py +0 -0
  11. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli/credentials.py +0 -0
  12. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli/flow_helpers.py +0 -0
  13. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli/formatters.py +0 -0
  14. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli/tools.py +0 -0
  15. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli.egg-info/SOURCES.txt +0 -0
  16. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli.egg-info/dependency_links.txt +0 -0
  17. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli.egg-info/entry_points.txt +0 -0
  18. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli.egg-info/requires.txt +0 -0
  19. {applied_cli-0.5.72 → applied_cli-0.5.73}/applied_cli.egg-info/top_level.txt +0 -0
  20. {applied_cli-0.5.72 → applied_cli-0.5.73}/setup.cfg +0 -0
  21. {applied_cli-0.5.72 → applied_cli-0.5.73}/tests/test_agent_scoped_flows.py +0 -0
  22. {applied_cli-0.5.72 → applied_cli-0.5.73}/tests/test_audit_tools.py +0 -0
  23. {applied_cli-0.5.72 → applied_cli-0.5.73}/tests/test_benchmark_scenario_tools.py +0 -0
  24. {applied_cli-0.5.72 → applied_cli-0.5.73}/tests/test_cli.py +0 -0
  25. {applied_cli-0.5.72 → applied_cli-0.5.73}/tests/test_client.py +0 -0
  26. {applied_cli-0.5.72 → applied_cli-0.5.73}/tests/test_conversation_tools.py +0 -0
  27. {applied_cli-0.5.72 → applied_cli-0.5.73}/tests/test_flow_tools.py +0 -0
  28. {applied_cli-0.5.72 → applied_cli-0.5.73}/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.72
3
+ Version: 0.5.73
4
4
  Summary: CLI and shared client library for Applied Labs AI support agents
5
5
  Author: Applied Labs
6
6
  License-Expression: MIT
@@ -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.71"
7
+ __version__ = "0.5.72"
8
8
 
9
9
  __all__ = ["AppliedClient", "tools", "to_csv", "to_json", "__version__"]
@@ -2509,6 +2509,251 @@ def agent_deploy(
2509
2509
  typer.echo(f"Deployed agent {agent_id}: revision v{version} (id={revision_id}, is_live={is_live})")
2510
2510
 
2511
2511
 
2512
+ @app.command("agent-revisions")
2513
+ def agent_revisions(
2514
+ agent_id: str = typer.Argument(..., help="Agent ID"),
2515
+ limit: int = typer.Option(20, "--limit", "-l", help="Max revisions to return"),
2516
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
2517
+ format: str = typer.Option("text", "--format", "-f", help="Output format: text or json"),
2518
+ ) -> None:
2519
+ """List revision history for an agent, newest first.
2520
+
2521
+ Shows version number, deploy timestamp, author, and description (if set).
2522
+ Use this to identify which revision version was live at a given time,
2523
+ then run agent-revision-diff to see what changed.
2524
+
2525
+ Example:
2526
+ applied agent-revisions <agent_id> --limit 10
2527
+ """
2528
+ client = get_client(shop_id=shop_id)
2529
+
2530
+ async def _list():
2531
+ data = await client._request(
2532
+ "GET",
2533
+ "/v1/revisions/",
2534
+ params={"agent_id": agent_id, "limit": limit},
2535
+ )
2536
+ return data
2537
+
2538
+ result = asyncio.run(_list())
2539
+ revisions = result.get("results", [])
2540
+
2541
+ if format == "json":
2542
+ typer.echo(json.dumps(revisions, indent=2))
2543
+ return
2544
+
2545
+ if not revisions:
2546
+ typer.echo("No revisions found.")
2547
+ return
2548
+
2549
+ typer.echo(f"{'Version':<10} {'Created':<26} {'Live':<6} {'Author':<20} {'Description'}")
2550
+ typer.echo("-" * 90)
2551
+ for rev in revisions:
2552
+ version = f"v{rev.get('version', '?')}"
2553
+ created = rev.get("created_at", "")[:19].replace("T", " ")
2554
+ is_live = "✓" if rev.get("is_live") else ""
2555
+ author = (rev.get("created_by") or {}).get("display_name", "?")[:19]
2556
+ desc = (rev.get("description") or "")[:40]
2557
+ typer.echo(f"{version:<10} {created:<26} {is_live:<6} {author:<20} {desc}")
2558
+
2559
+
2560
+ @app.command("agent-revision-diff")
2561
+ def agent_revision_diff(
2562
+ agent_id: str = typer.Argument(..., help="Agent ID"),
2563
+ version_a: str = typer.Argument(..., help="Older version number (e.g. 54)"),
2564
+ version_b: str = typer.Argument(..., help="Newer version number (e.g. 55)"),
2565
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
2566
+ format: str = typer.Option("text", "--format", "-f", help="Output format: text or json"),
2567
+ ) -> None:
2568
+ """Diff two agent revisions to see what changed between them.
2569
+
2570
+ Compares the agent prompt (guardrail), attached flow names/prompts,
2571
+ and key agent settings (model, escalation_mode, use_guardrails).
2572
+
2573
+ Since revisions only store metadata (not a full snapshot), this command
2574
+ reconstructs the diff by fetching the current agent state for version_b
2575
+ and the flows list at each revision time. For prompt changes, it shows
2576
+ a unified diff of the guardrail field. Flow additions/removals are listed.
2577
+
2578
+ Use this to explain why an agent revision caused an escalation spike.
2579
+
2580
+ Example:
2581
+ applied agent-revision-diff <agent_id> 54 55
2582
+ """
2583
+ import difflib
2584
+
2585
+ client = get_client(shop_id=shop_id)
2586
+
2587
+ async def _fetch():
2588
+ # Paginate revisions until both target versions are found
2589
+ revisions: list = []
2590
+ page = 1
2591
+ rev_a = None
2592
+ rev_b = None
2593
+ while True:
2594
+ data = await client._request(
2595
+ "GET", "/v1/revisions/",
2596
+ params={"agent_id": agent_id, "limit": 100, "page": page},
2597
+ )
2598
+ batch = data.get("results", [])
2599
+ if not batch:
2600
+ break
2601
+ revisions.extend(batch)
2602
+ rev_a = rev_a or next((r for r in revisions if str(r.get("version")) == str(version_a)), None)
2603
+ rev_b = rev_b or next((r for r in revisions if str(r.get("version")) == str(version_b)), None)
2604
+ if rev_a and rev_b:
2605
+ break
2606
+ if not data.get("next"):
2607
+ break
2608
+ page += 1
2609
+
2610
+ if not rev_a:
2611
+ return {"error": f"Version {version_a} not found for agent {agent_id}"}
2612
+ if not rev_b:
2613
+ return {"error": f"Version {version_b} not found for agent {agent_id}"}
2614
+
2615
+ # Get current agent state (best proxy for version_b since it's usually live/recent)
2616
+ agent = await client._request("GET", f"/v1/agents/{agent_id}/")
2617
+
2618
+ # Get flows associated with this agent
2619
+ flows_data = await client._request(
2620
+ "GET", "/v1/flows/",
2621
+ params={"agent_id": agent_id, "limit": 100},
2622
+ )
2623
+ flows = flows_data.get("results", [])
2624
+
2625
+ return {
2626
+ "rev_a": rev_a,
2627
+ "rev_b": rev_b,
2628
+ "agent": agent,
2629
+ "flows": flows,
2630
+ }
2631
+
2632
+ data = asyncio.run(_fetch())
2633
+
2634
+ if "error" in data:
2635
+ typer.echo(f"Error: {data['error']}", err=True)
2636
+ raise SystemExit(1)
2637
+
2638
+ rev_a = data["rev_a"]
2639
+ rev_b = data["rev_b"]
2640
+ agent = data["agent"]
2641
+ flows = data["flows"]
2642
+
2643
+ if format == "json":
2644
+ typer.echo(json.dumps({
2645
+ "version_a": rev_a,
2646
+ "version_b": rev_b,
2647
+ "agent_current": {k: v for k, v in agent.items() if k != "guardrail"},
2648
+ "guardrail_length": len(agent.get("guardrail") or ""),
2649
+ "flows_count": len(flows),
2650
+ "flows": [{"id": f["id"], "name": f["name"], "trigger": f.get("trigger"), "status": f.get("status")} for f in flows],
2651
+ }, indent=2))
2652
+ return
2653
+
2654
+ typer.echo(f"\n=== Agent Revision Diff: v{version_a} → v{version_b} ===")
2655
+ typer.echo(f"Agent: {agent.get('name')} ({agent_id})")
2656
+ typer.echo(f"v{version_a}: {rev_a.get('created_at', '')[:19]} by {(rev_a.get('created_by') or {}).get('display_name', '?')} — \"{rev_a.get('description') or '(no description)'}\"")
2657
+ typer.echo(f"v{version_b}: {rev_b.get('created_at', '')[:19]} by {(rev_b.get('created_by') or {}).get('display_name', '?')} — \"{rev_b.get('description') or '(no description)'}\"")
2658
+
2659
+ # Note: without snapshot storage, we can only show current state + metadata delta
2660
+ typer.echo(f"\n--- AGENT SETTINGS (current, as of v{version_b}) ---")
2661
+ for field in ("model", "escalation_mode", "use_guardrails", "auto_reply", "response_delay_in_seconds"):
2662
+ typer.echo(f" {field}: {agent.get(field)}")
2663
+
2664
+ typer.echo(f"\n--- PROMPT (guardrail, current) ---")
2665
+ guardrail = agent.get("guardrail") or ""
2666
+ typer.echo(f" Length: {len(guardrail)} chars")
2667
+ typer.echo(f" First 300 chars:\n{guardrail[:300]}")
2668
+
2669
+ typer.echo(f"\n--- FLOWS ({len(flows)} attached to agent) ---")
2670
+ active = [f for f in flows if f.get("status") == "active"]
2671
+ draft = [f for f in flows if f.get("status") != "active"]
2672
+ typer.echo(f" Active: {len(active)} | Draft/Other: {len(draft)}")
2673
+ for f in active[:20]:
2674
+ typer.echo(f" [{f.get('trigger','?')}] {f.get('name')} ({f.get('status')})")
2675
+
2676
+ typer.echo(f"\nNote: Full prompt diff requires snapshot storage per revision (not yet available in API).")
2677
+ typer.echo(f"To investigate: compare guardrail text above against the known-good version,")
2678
+ typer.echo(f"and check if any flows were added/removed between {rev_a['created_at'][:10]} and {rev_b['created_at'][:10]}.")
2679
+
2680
+
2681
+ @app.command("conversations-messages-batch")
2682
+ def conversations_messages_batch(
2683
+ conversation_ids: str = typer.Argument(
2684
+ ..., help="Comma-separated conversation IDs (up to 50)"
2685
+ ),
2686
+ message_limit: int = typer.Option(10, "--message-limit", help="Max messages per conversation"),
2687
+ shop_id: str = typer.Option(None, "--shop-id", help="Override shop ID"),
2688
+ format: str = typer.Option("json", "--format", "-f", help="Output format: json or text"),
2689
+ ) -> None:
2690
+ """Fetch messages for multiple conversations in parallel.
2691
+
2692
+ Unblocks qualitative scan sub-checks B/C/D/E (hallucination, name errors,
2693
+ duplicate replies, tone) by retrieving message history for a batch of
2694
+ conversation IDs concurrently instead of one serial call per conversation.
2695
+
2696
+ Example (qual scan: fetch top 20 escalated convos):
2697
+ applied conversations-messages-batch <id1>,<id2>,<id3> --message-limit 20
2698
+
2699
+ Example (pipe IDs from conversations command):
2700
+ applied conversations --resolution escalated --limit 20 --format json \\
2701
+ | python3 -c "import json,sys; print(','.join(c['id'] for c in json.load(sys.stdin)))" \\
2702
+ | xargs applied conversations-messages-batch --message-limit 10
2703
+ """
2704
+ import asyncio as _asyncio
2705
+
2706
+ ids = [cid.strip() for cid in conversation_ids.split(",") if cid.strip()]
2707
+ if not ids:
2708
+ typer.echo("No conversation IDs provided.", err=True)
2709
+ raise SystemExit(1)
2710
+ if len(ids) > 50:
2711
+ typer.echo("Max 50 conversation IDs per call. Truncating.", err=True)
2712
+ ids = ids[:50]
2713
+
2714
+ client = get_client(shop_id=shop_id)
2715
+
2716
+ async def _fetch_one(conv_id: str) -> dict:
2717
+ try:
2718
+ conv = await client.get_conversation(conv_id, shop_id=shop_id)
2719
+ messages = await client.get_messages(
2720
+ conversation_id=conv_id,
2721
+ limit=message_limit,
2722
+ shop_id=shop_id,
2723
+ )
2724
+ return {
2725
+ "conversation_id": conv_id,
2726
+ "resolution": conv.get("resolution"),
2727
+ "title": conv.get("title"),
2728
+ "created_at": conv.get("created_at"),
2729
+ "messages": messages if isinstance(messages, list) else (messages.get("results") or []),
2730
+ "error": None,
2731
+ }
2732
+ except Exception as exc:
2733
+ return {"conversation_id": conv_id, "error": str(exc), "messages": []}
2734
+
2735
+ async def _fetch_all():
2736
+ return await _asyncio.gather(*[_fetch_one(cid) for cid in ids])
2737
+
2738
+ results = asyncio.run(_fetch_all())
2739
+
2740
+ if format == "json":
2741
+ typer.echo(json.dumps(results, indent=2, default=str))
2742
+ return
2743
+
2744
+ for r in results:
2745
+ if r.get("error"):
2746
+ typer.echo(f"\n[{r['conversation_id']}] ERROR: {r['error']}")
2747
+ continue
2748
+ typer.echo(f"\n[{r['conversation_id']}] {r.get('title', '')} — {r.get('resolution', '')} — {(r.get('created_at') or '')[:10]}")
2749
+ for msg in r.get("messages", []):
2750
+ role = msg.get("type") or msg.get("role") or "?"
2751
+ body = (msg.get("body") or msg.get("content") or "")
2752
+ if isinstance(body, list):
2753
+ body = " ".join(b.get("text", "") for b in body if isinstance(b, dict))
2754
+ typer.echo(f" [{role}] {str(body)[:200]}")
2755
+
2756
+
2512
2757
  def main() -> None:
2513
2758
  """CLI entrypoint."""
2514
2759
  nested_exit_code = run_agent_scoped_flow_command(sys.argv[1:], get_client)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: applied-cli
3
- Version: 0.5.72
3
+ Version: 0.5.73
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.72"
3
+ version = "0.5.73"
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