loopview 0.1.0__tar.gz → 0.1.1__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 (53) hide show
  1. {loopview-0.1.0 → loopview-0.1.1}/PKG-INFO +1 -1
  2. {loopview-0.1.0 → loopview-0.1.1}/pyproject.toml +1 -1
  3. {loopview-0.1.0 → loopview-0.1.1}/pyproject.toml.orig +1 -1
  4. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/__init__.py +1 -1
  5. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/cli.py +25 -5
  6. loopview-0.1.1/src/loopview/static/assets/index-CkzKKDRu.js +21 -0
  7. loopview-0.1.1/src/loopview/static/assets/index-CnQIIz-O.css +1 -0
  8. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/index.html +2 -2
  9. loopview-0.1.0/src/loopview/static/assets/index-CEGppBgk.css +0 -1
  10. loopview-0.1.0/src/loopview/static/assets/index-DzC96LiQ.js +0 -21
  11. {loopview-0.1.0 → loopview-0.1.1}/README.md +0 -0
  12. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/app.py +0 -0
  13. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/cost/__init__.py +0 -0
  14. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/cost/pricing.json +0 -0
  15. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/cost/pricing.py +0 -0
  16. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/cost/split.py +0 -0
  17. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/demo_data/flagship.otlp.jsonl +0 -0
  18. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/devtools/__init__.py +0 -0
  19. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/devtools/dump_normalized.py +0 -0
  20. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/ingest/__init__.py +0 -0
  21. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/ingest/otlp.py +0 -0
  22. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/ingest/raw.py +0 -0
  23. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/live.py +0 -0
  24. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/normalize/__init__.py +0 -0
  25. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/normalize/adapters/__init__.py +0 -0
  26. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/normalize/adapters/base.py +0 -0
  27. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/normalize/adapters/gen_ai.py +0 -0
  28. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/normalize/adapters/generic.py +0 -0
  29. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/normalize/adapters/openinference.py +0 -0
  30. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/normalize/derived_tools.py +0 -0
  31. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/normalize/loop_nodes.py +0 -0
  32. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/normalize/normalizer.py +0 -0
  33. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/normalize/schema.py +0 -0
  34. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/normalize/transitions.py +0 -0
  35. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/py.typed +0 -0
  36. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/elk-worker.min-DfmSo98M.js +0 -0
  37. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  38. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  39. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  40. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  41. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  42. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  43. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  44. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  45. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  46. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  47. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  48. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/static/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  49. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/store/__init__.py +0 -0
  50. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/store/capture.py +0 -0
  51. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/store/memory.py +0 -0
  52. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/tools/__init__.py +0 -0
  53. {loopview-0.1.0 → loopview-0.1.1}/src/loopview/tools/report.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopview
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Watch your AI agents run as a live graph, from any OpenTelemetry trace.
5
5
  Keywords: ai-agents,llm,opentelemetry,observability,debugging,tracing
6
6
  Author: YasmineZerai
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "loopview"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  description = "Watch your AI agents run as a live graph, from any OpenTelemetry trace."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "loopview"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  description = "Watch your AI agents run as a live graph, from any OpenTelemetry trace."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -1,3 +1,3 @@
1
1
  """loopview: watch AI agents run as a live graph, from any OpenTelemetry trace."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.1"
@@ -6,6 +6,7 @@ so an extra dependency is not worth it.
6
6
 
7
7
  import argparse
8
8
  import logging
9
+ import socket
9
10
  import threading
10
11
  import webbrowser
11
12
  from pathlib import Path
@@ -82,16 +83,27 @@ def main(argv: list[str] | None = None) -> None:
82
83
  # Flush now: when stdout is a pipe, Python buffers it and the banner would
83
84
  # only appear when the server stops.
84
85
  print(flush=True)
86
+ open_url = None
85
87
  if args.command == "demo" and not args.no_browser:
86
- # Give the server a moment to start listening before the browser asks.
87
- url = f"http://{args.host}:{args.port}"
88
- threading.Timer(1.0, webbrowser.open, args=[url]).start()
88
+ open_url = f"http://{args.host}:{args.port}"
89
89
  app = create_app(store=store, capture=capture, pricing=merged_pricing(args.prices))
90
90
  server = _Server(
91
- uvicorn.Config(app, host=args.host, port=args.port, log_level="warning"), app.state.hub
91
+ uvicorn.Config(app, host=args.host, port=args.port, log_level="warning"),
92
+ app.state.hub,
93
+ open_url,
92
94
  )
93
95
  try:
94
96
  server.run()
97
+ except SystemExit:
98
+ # uvicorn has logged the reason (usually the port is taken) and exits.
99
+ if not server.started:
100
+ print(
101
+ f"\nloopview could not listen on {args.host}:{args.port}. If another loopview "
102
+ "(or another OpenTelemetry collector) is using that port, stop it or choose "
103
+ "another one with --port.",
104
+ flush=True,
105
+ )
106
+ raise
95
107
  except KeyboardInterrupt:
96
108
  # uvicorn re-raises Ctrl+C once it has shut down cleanly; that's the
97
109
  # normal way out, not an error.
@@ -106,9 +118,17 @@ class _Server(uvicorn.Server):
106
118
  pressed. Otherwise it would wait for the browser's open event stream, which
107
119
  never closes on its own, and then cancel it with a traceback."""
108
120
 
109
- def __init__(self, config: uvicorn.Config, hub: LiveHub) -> None:
121
+ def __init__(self, config: uvicorn.Config, hub: LiveHub, open_url: str | None = None) -> None:
110
122
  super().__init__(config)
111
123
  self.hub = hub
124
+ self.open_url = open_url
125
+
126
+ async def startup(self, sockets: list[socket.socket] | None = None) -> None:
127
+ await super().startup(sockets)
128
+ # Open the browser only once the port is ours: if it was taken, the page
129
+ # would show whatever else is listening there.
130
+ if self.started and self.open_url:
131
+ threading.Thread(target=webbrowser.open, args=[self.open_url], daemon=True).start()
112
132
 
113
133
  def handle_exit(self, sig: int, frame: FrameType | None) -> None:
114
134
  self.hub.close()