traceact 0.1.1__tar.gz → 0.2.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 (34) hide show
  1. traceact-0.2.1/PKG-INFO +187 -0
  2. traceact-0.2.1/README.md +157 -0
  3. {traceact-0.1.1 → traceact-0.2.1}/USAGE.md +227 -1
  4. {traceact-0.1.1 → traceact-0.2.1}/pyproject.toml +11 -1
  5. {traceact-0.1.1 → traceact-0.2.1}/traceact/__init__.py +3 -0
  6. {traceact-0.1.1 → traceact-0.2.1}/traceact/budget.py +33 -0
  7. traceact-0.2.1/traceact/sinks.py +481 -0
  8. traceact-0.2.1/traceact/viewer/__init__.py +15 -0
  9. traceact-0.2.1/traceact/viewer/cli.py +187 -0
  10. traceact-0.2.1/traceact/viewer/instance.py +169 -0
  11. traceact-0.2.1/traceact/viewer/reader.py +246 -0
  12. traceact-0.2.1/traceact/viewer/server.py +414 -0
  13. traceact-0.2.1/traceact/viewer/static/app.js +845 -0
  14. traceact-0.2.1/traceact/viewer/static/index.html +188 -0
  15. traceact-0.2.1/traceact/viewer/static/styles.css +654 -0
  16. traceact-0.2.1/traceact.egg-info/PKG-INFO +187 -0
  17. {traceact-0.1.1 → traceact-0.2.1}/traceact.egg-info/SOURCES.txt +10 -1
  18. traceact-0.2.1/traceact.egg-info/entry_points.txt +2 -0
  19. traceact-0.1.1/PKG-INFO +0 -99
  20. traceact-0.1.1/README.md +0 -70
  21. traceact-0.1.1/traceact/sinks.py +0 -177
  22. traceact-0.1.1/traceact.egg-info/PKG-INFO +0 -99
  23. {traceact-0.1.1 → traceact-0.2.1}/LICENSE +0 -0
  24. {traceact-0.1.1 → traceact-0.2.1}/MANIFEST.in +0 -0
  25. {traceact-0.1.1 → traceact-0.2.1}/setup.cfg +0 -0
  26. {traceact-0.1.1 → traceact-0.2.1}/traceact/config.py +0 -0
  27. {traceact-0.1.1 → traceact-0.2.1}/traceact/context.py +0 -0
  28. {traceact-0.1.1 → traceact-0.2.1}/traceact/decorators.py +0 -0
  29. {traceact-0.1.1 → traceact-0.2.1}/traceact/helpers.py +0 -0
  30. {traceact-0.1.1 → traceact-0.2.1}/traceact/ids.py +0 -0
  31. {traceact-0.1.1 → traceact-0.2.1}/traceact/trace.py +0 -0
  32. {traceact-0.1.1 → traceact-0.2.1}/traceact.egg-info/dependency_links.txt +0 -0
  33. {traceact-0.1.1 → traceact-0.2.1}/traceact.egg-info/requires.txt +0 -0
  34. {traceact-0.1.1 → traceact-0.2.1}/traceact.egg-info/top_level.txt +0 -0
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.4
2
+ Name: traceact
3
+ Version: 0.2.1
4
+ Summary: X-ray vision for your code. Lightweight action-level tracing for Python.
5
+ Author: Mohammed Shehu
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/traceact/traceact
8
+ Project-URL: Repository, https://github.com/traceact/traceact
9
+ Project-URL: Documentation, https://github.com/traceact/traceact/blob/main/USAGE.md
10
+ Project-URL: Changelog, https://github.com/traceact/traceact/blob/main/CHANGELOG.md
11
+ Project-URL: Issues, https://github.com/traceact/traceact/issues
12
+ Project-URL: Author, https://mohammedshehu.com
13
+ Keywords: tracing,observability,debugging,devtools,ai-agents
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Debuggers
22
+ Classifier: Topic :: System :: Logging
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0; extra == "dev"
28
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # TraceAct
32
+
33
+ [![PyPI version](https://img.shields.io/pypi/v/traceact.svg)](https://pypi.org/project/traceact/)
34
+ [![Python versions](https://img.shields.io/pypi/pyversions/traceact.svg)](https://pypi.org/project/traceact/)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
36
+
37
+ X-ray vision for Python code.
38
+
39
+ TraceAct is a lightweight Python package for action-level tracing. It records the full story of what happens when a function runs — every step taken, resource touched, event recorded, and failure encountered — so you or your agent can understand what actually happened.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install traceact
45
+ ```
46
+
47
+ ## Quick start
48
+
49
+ ```python
50
+ from traceact import traced_action, configure, TraceConfig, JsonlSink
51
+
52
+ configure(
53
+ config=TraceConfig(sink_mode="blocking"),
54
+ sinks=[JsonlSink("data/traces.jsonl")],
55
+ )
56
+
57
+ @traced_action(action="note.create", kind="app", actor="user")
58
+ def create_note(title, body):
59
+ ...
60
+ ```
61
+
62
+ Each traced function call produces one JSON object appended to the JSONL file. Open the viewer to explore it live.
63
+
64
+ ## Manual tracing
65
+
66
+ ```python
67
+ from traceact import ActionTrace
68
+
69
+ with ActionTrace.start(action="note.create", kind="app") as trace:
70
+ trace.input({"title": "Hello"})
71
+ trace.step("Validated input")
72
+ trace.event(kind="db", operation="insert", target="notes")
73
+ trace.output({"note_id": "note_123"})
74
+ ```
75
+
76
+ ## The viewer
77
+
78
+ TraceAct includes a local web viewer. No extra install — it ships with the package.
79
+
80
+ ```bash
81
+ traceact view data/traces.jsonl
82
+ ```
83
+
84
+ This starts a server at `http://127.0.0.1:8765` and opens your browser. The viewer tails the file live: traces appear as your app writes them.
85
+
86
+ ### Source types
87
+
88
+ | What you pass | What happens |
89
+ |---|---|
90
+ | A `.jsonl` file | Tails that file live |
91
+ | A folder | Merges all `.jsonl` files inside (e.g. per-process shards) |
92
+ | Nothing | Opens empty; use the in-app modal to add a source |
93
+
94
+ ### CLI flags
95
+
96
+ ```bash
97
+ traceact view [SOURCE] [--port N] [--host HOST] [--no-browser] [--new]
98
+ traceact show [SOURCE] ... # identical alias of view
99
+ ```
100
+
101
+ | Flag | Default | Effect |
102
+ |---|---|---|
103
+ | `--port N` | `8765` | Port to serve on |
104
+ | `--host HOST` | `127.0.0.1` | Interface to bind (localhost only by default) |
105
+ | `--no-browser` | off | Start the server without opening a browser tab |
106
+ | `--new` | off | Force a fresh instance even if one is already running |
107
+
108
+ ### Port selection
109
+
110
+ The viewer auto-increments the port if the requested one is taken. If you ask for `8765` and it's in use, it tries `8766`, `8767`, and so on up to 20 times before giving up. Pass `--port` to start from a different base.
111
+
112
+ ### Single-instance behaviour
113
+
114
+ Running `traceact view` a second time reuses an already-running viewer rather than starting a second server. The new source (if given) is added to the running viewer and a browser tab is opened on it. This means you can call `traceact view path/to/new-file.jsonl` from multiple terminal tabs during a session and they all feed into one viewer.
115
+
116
+ Pass `--new` to bypass this and force a second independent instance.
117
+
118
+ ### macOS launcher
119
+
120
+ Double-click `launch.command` in the repo root to open TraceAct from Finder without a terminal. It checks for a running instance first, then creates a `.venv/`, installs or upgrades `traceact`, and opens the browser.
121
+
122
+ ### Adding sources in the app
123
+
124
+ The source modal (click the source name in the header) lets you:
125
+
126
+ - **Choose file / Choose folder** — opens a native macOS picker; returns the real filesystem path for live tailing
127
+ - **Drag and drop** a `.jsonl` file — saved as a static snapshot in `~/.traceact/imports/`
128
+ - **Type a path** — collapsible fallback for pasting an absolute path
129
+
130
+ ## Concepts
131
+
132
+ | Concept | Meaning |
133
+ |---|---|
134
+ | `Trace` | The full record of one action (function call) |
135
+ | `Step` | A human-readable timeline marker within a trace |
136
+ | `Event` | A structured operation: db, http, file, model, job, etc. |
137
+ | `Touch` | A resource involved in the trace (auto-derived from events) |
138
+ | `Sink` | Where completed traces are written (`JsonlSink`, `ConsoleSink`) |
139
+
140
+ ## Wiring into a web app
141
+
142
+ If your app has its own UI, add a backend route to launch or connect to the viewer, then call it from a button:
143
+
144
+ ```python
145
+ # FastAPI — launch_or_connect is blocking, so use run_in_executor
146
+ import asyncio
147
+ from traceact.viewer.instance import launch_or_connect
148
+
149
+ @router.get("/api/launch-viewer")
150
+ async def launch_viewer():
151
+ loop = asyncio.get_event_loop()
152
+ url = await loop.run_in_executor(None, launch_or_connect,
153
+ "data/traces/traces.jsonl")
154
+ return {"url": url}
155
+ ```
156
+
157
+ ```javascript
158
+ // Frontend button
159
+ document.getElementById("btn-viewer").addEventListener("click", async () => {
160
+ const btn = document.getElementById("btn-viewer");
161
+ btn.disabled = true;
162
+ try {
163
+ const { url } = await fetch("/api/launch-viewer").then(r => r.json());
164
+ window.open(url, "_blank", "noopener");
165
+ } finally {
166
+ btn.disabled = false;
167
+ }
168
+ });
169
+ ```
170
+
171
+ `launch_or_connect` checks for a running viewer first (via `~/.traceact/viewer.json` + a health probe). If one is found, it adds your source to it and returns the URL immediately — no new process. If nothing is running, it spawns the viewer as a background subprocess and waits up to 3 s for it to be ready.
172
+
173
+ ## Requirements
174
+
175
+ Python 3.9+. No runtime dependencies.
176
+
177
+ ## Full reference
178
+
179
+ See [USAGE.md](USAGE.md) for complete API documentation: all decorator and context manager parameters, helper methods (`trace.db`, `trace.http`, `trace.file`, `trace.model`), input capture, parent/child traces, sinks, budget configuration, the trace record schema, test isolation, and the full viewer server API.
180
+
181
+ ## License
182
+
183
+ MIT
184
+
185
+ ---
186
+
187
+ Built by [Mo Shehu](https://mohammedshehu.com).
@@ -0,0 +1,157 @@
1
+ # TraceAct
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/traceact.svg)](https://pypi.org/project/traceact/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/traceact.svg)](https://pypi.org/project/traceact/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ X-ray vision for Python code.
8
+
9
+ TraceAct is a lightweight Python package for action-level tracing. It records the full story of what happens when a function runs — every step taken, resource touched, event recorded, and failure encountered — so you or your agent can understand what actually happened.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install traceact
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ```python
20
+ from traceact import traced_action, configure, TraceConfig, JsonlSink
21
+
22
+ configure(
23
+ config=TraceConfig(sink_mode="blocking"),
24
+ sinks=[JsonlSink("data/traces.jsonl")],
25
+ )
26
+
27
+ @traced_action(action="note.create", kind="app", actor="user")
28
+ def create_note(title, body):
29
+ ...
30
+ ```
31
+
32
+ Each traced function call produces one JSON object appended to the JSONL file. Open the viewer to explore it live.
33
+
34
+ ## Manual tracing
35
+
36
+ ```python
37
+ from traceact import ActionTrace
38
+
39
+ with ActionTrace.start(action="note.create", kind="app") as trace:
40
+ trace.input({"title": "Hello"})
41
+ trace.step("Validated input")
42
+ trace.event(kind="db", operation="insert", target="notes")
43
+ trace.output({"note_id": "note_123"})
44
+ ```
45
+
46
+ ## The viewer
47
+
48
+ TraceAct includes a local web viewer. No extra install — it ships with the package.
49
+
50
+ ```bash
51
+ traceact view data/traces.jsonl
52
+ ```
53
+
54
+ This starts a server at `http://127.0.0.1:8765` and opens your browser. The viewer tails the file live: traces appear as your app writes them.
55
+
56
+ ### Source types
57
+
58
+ | What you pass | What happens |
59
+ |---|---|
60
+ | A `.jsonl` file | Tails that file live |
61
+ | A folder | Merges all `.jsonl` files inside (e.g. per-process shards) |
62
+ | Nothing | Opens empty; use the in-app modal to add a source |
63
+
64
+ ### CLI flags
65
+
66
+ ```bash
67
+ traceact view [SOURCE] [--port N] [--host HOST] [--no-browser] [--new]
68
+ traceact show [SOURCE] ... # identical alias of view
69
+ ```
70
+
71
+ | Flag | Default | Effect |
72
+ |---|---|---|
73
+ | `--port N` | `8765` | Port to serve on |
74
+ | `--host HOST` | `127.0.0.1` | Interface to bind (localhost only by default) |
75
+ | `--no-browser` | off | Start the server without opening a browser tab |
76
+ | `--new` | off | Force a fresh instance even if one is already running |
77
+
78
+ ### Port selection
79
+
80
+ The viewer auto-increments the port if the requested one is taken. If you ask for `8765` and it's in use, it tries `8766`, `8767`, and so on up to 20 times before giving up. Pass `--port` to start from a different base.
81
+
82
+ ### Single-instance behaviour
83
+
84
+ Running `traceact view` a second time reuses an already-running viewer rather than starting a second server. The new source (if given) is added to the running viewer and a browser tab is opened on it. This means you can call `traceact view path/to/new-file.jsonl` from multiple terminal tabs during a session and they all feed into one viewer.
85
+
86
+ Pass `--new` to bypass this and force a second independent instance.
87
+
88
+ ### macOS launcher
89
+
90
+ Double-click `launch.command` in the repo root to open TraceAct from Finder without a terminal. It checks for a running instance first, then creates a `.venv/`, installs or upgrades `traceact`, and opens the browser.
91
+
92
+ ### Adding sources in the app
93
+
94
+ The source modal (click the source name in the header) lets you:
95
+
96
+ - **Choose file / Choose folder** — opens a native macOS picker; returns the real filesystem path for live tailing
97
+ - **Drag and drop** a `.jsonl` file — saved as a static snapshot in `~/.traceact/imports/`
98
+ - **Type a path** — collapsible fallback for pasting an absolute path
99
+
100
+ ## Concepts
101
+
102
+ | Concept | Meaning |
103
+ |---|---|
104
+ | `Trace` | The full record of one action (function call) |
105
+ | `Step` | A human-readable timeline marker within a trace |
106
+ | `Event` | A structured operation: db, http, file, model, job, etc. |
107
+ | `Touch` | A resource involved in the trace (auto-derived from events) |
108
+ | `Sink` | Where completed traces are written (`JsonlSink`, `ConsoleSink`) |
109
+
110
+ ## Wiring into a web app
111
+
112
+ If your app has its own UI, add a backend route to launch or connect to the viewer, then call it from a button:
113
+
114
+ ```python
115
+ # FastAPI — launch_or_connect is blocking, so use run_in_executor
116
+ import asyncio
117
+ from traceact.viewer.instance import launch_or_connect
118
+
119
+ @router.get("/api/launch-viewer")
120
+ async def launch_viewer():
121
+ loop = asyncio.get_event_loop()
122
+ url = await loop.run_in_executor(None, launch_or_connect,
123
+ "data/traces/traces.jsonl")
124
+ return {"url": url}
125
+ ```
126
+
127
+ ```javascript
128
+ // Frontend button
129
+ document.getElementById("btn-viewer").addEventListener("click", async () => {
130
+ const btn = document.getElementById("btn-viewer");
131
+ btn.disabled = true;
132
+ try {
133
+ const { url } = await fetch("/api/launch-viewer").then(r => r.json());
134
+ window.open(url, "_blank", "noopener");
135
+ } finally {
136
+ btn.disabled = false;
137
+ }
138
+ });
139
+ ```
140
+
141
+ `launch_or_connect` checks for a running viewer first (via `~/.traceact/viewer.json` + a health probe). If one is found, it adds your source to it and returns the URL immediately — no new process. If nothing is running, it spawns the viewer as a background subprocess and waits up to 3 s for it to be ready.
142
+
143
+ ## Requirements
144
+
145
+ Python 3.9+. No runtime dependencies.
146
+
147
+ ## Full reference
148
+
149
+ See [USAGE.md](USAGE.md) for complete API documentation: all decorator and context manager parameters, helper methods (`trace.db`, `trace.http`, `trace.file`, `trace.model`), input capture, parent/child traces, sinks, budget configuration, the trace record schema, test isolation, and the full viewer server API.
150
+
151
+ ## License
152
+
153
+ MIT
154
+
155
+ ---
156
+
157
+ Built by [Mo Shehu](https://mohammedshehu.com).
@@ -1,6 +1,32 @@
1
1
  # TraceAct Usage Reference
2
2
 
3
- Full API documentation for TraceAct v0.1.0.
3
+ Full API and CLI documentation for TraceAct.
4
+
5
+ ## Package layout
6
+
7
+ ```
8
+ traceact/
9
+ __init__.py — public exports
10
+ trace.py — ActionTrace class, core lifecycle
11
+ decorators.py — @traced_action decorator (sync + async)
12
+ config.py — TraceConfig, configure(), reset_config()
13
+ budget.py — TraceBudget, TraceBudget.production() preset
14
+ context.py — ContextVar for active trace, SKIP sentinel
15
+ sinks.py — JsonlSink (thread-safe), ConsoleSink, AsyncSink (not yet public)
16
+ helpers.py — TraceHelpersMixin (trace.db, trace.http, trace.file, trace.model)
17
+ ids.py — ID generation (trc_, evt_, stp_, corr_ prefixes)
18
+
19
+ viewer/
20
+ cli.py — `traceact view` / `traceact show` CLI entry point; --new flag
21
+ server.py — stdlib ThreadingHTTPServer; SPA + REST + SSE
22
+ reader.py — SourceReader: JSONL snapshot + live byte-offset tail
23
+ instance.py — single-instance coordination (state file + HTTP probe);
24
+ launch_or_connect() for embedding in app backends
25
+ static/
26
+ index.html — single-page app shell
27
+ styles.css — design system (dark theme, CSS custom properties)
28
+ app.js — all frontend logic (no framework, no build step)
29
+ ```
4
30
 
5
31
  ## Contents
6
32
 
@@ -21,6 +47,8 @@ Full API documentation for TraceAct v0.1.0.
21
47
  15. [TraceConfig fields](#traceconfig-fields)
22
48
  16. [Test isolation](#test-isolation)
23
49
  17. [Trace record schema](#trace-record-schema)
50
+ 18. [Viewing traces](#viewing-traces)
51
+ 19. [Integrating the viewer into your app](#integrating-the-viewer-into-your-app)
24
52
 
25
53
  ---
26
54
 
@@ -486,6 +514,24 @@ def run_agent():
486
514
  ...
487
515
  ```
488
516
 
517
+ ### Production preset
518
+
519
+ The package default records every trace (`sample_rate=1.0`), which is right for development and first-run — you trace a function, run it once, and the trace is there. For high-volume production, `TraceBudget.production()` opts into a lighter footprint:
520
+
521
+ ```python
522
+ from traceact import configure, TraceBudget
523
+
524
+ configure(budget=TraceBudget.production())
525
+ # equivalent to TraceBudget(sample_rate=0.1, always_trace_errors=True)
526
+ ```
527
+
528
+ It records roughly 10% of successful traces while never dropping a failure. Only `sample_rate` and `always_trace_errors` are set; every other field inherits from the package default or a parent trace. Override a single field on top of the preset if needed:
529
+
530
+ ```python
531
+ budget = TraceBudget.production()
532
+ budget.sample_rate = 0.25 # record 25% instead of 10%
533
+ ```
534
+
489
535
  ---
490
536
 
491
537
  ## TraceConfig fields
@@ -619,6 +665,186 @@ The full JSON object written to the JSONL sink:
619
665
 
620
666
  ---
621
667
 
668
+ ## Viewing traces
669
+
670
+ TraceAct ships with a local, dependency-free web viewer. Installing the package gives you both the SDK and the `traceact` command — there is no separate viewer package to install.
671
+
672
+ ```bash
673
+ pip install traceact
674
+ traceact view data/traces/traces.jsonl
675
+ ```
676
+
677
+ This opens a browser at `http://127.0.0.1:8765` showing a live trace log, a trace map, and an inspector. The viewer tails the source, so traces appear as your app writes them.
678
+
679
+ ### Command
680
+
681
+ ```bash
682
+ traceact view [SOURCE] # open the viewer
683
+ traceact show [SOURCE] # alias of view (identical)
684
+ ```
685
+
686
+ `view` and `show` are interchangeable aliases of the same command.
687
+
688
+ `SOURCE` is optional and may be:
689
+
690
+ - a `.jsonl` file — `traceact view data/traces.jsonl`
691
+ - a folder of `.jsonl` files — `traceact view data/traces/` (merges every file inside, e.g. per-process shards or several apps' files)
692
+ - omitted — `traceact view` opens empty and prompts you to add a source
693
+
694
+ The viewer reads any line that parses as JSON and looks like a trace; malformed entries are skipped, so files being appended to concurrently are safe to read.
695
+
696
+ ### Flags
697
+
698
+ | Flag | Default | Meaning |
699
+ |---|---|---|
700
+ | `--port N` | `8765` | Port to serve on. Auto-increments if the port is taken. |
701
+ | `--host HOST` | `127.0.0.1` | Interface to bind. Localhost only by default. |
702
+ | `--no-browser` | off | Start the server without opening a browser tab. |
703
+ | `--new` | off | Force a new viewer instance even if one is already running. |
704
+
705
+ You can also run it as a module: `python -m traceact.viewer.cli view SOURCE`.
706
+
707
+ ### Single-instance behaviour
708
+
709
+ Running `traceact view` a second time — even for a different source — reuses a viewer that is already running rather than spawning a second server. The new source is added to the running viewer and a browser tab is opened on it. This avoids accumulating background processes across repeated launches during a dev session.
710
+
711
+ The coordination mechanism is a state file at `~/.traceact/viewer.json` that records the host and port of the running viewer. On each launch, TraceAct probes that address with a health check before deciding whether to reuse or start fresh. A stale state file (crashed or stopped viewer) is ignored and a new server starts normally.
712
+
713
+ Pass `--new` to bypass this and force a second instance — useful when you want two viewers side-by-side with different sources.
714
+
715
+ ### Adding sources in the viewer
716
+
717
+ The "Add source" modal supports three ways to load a source:
718
+
719
+ - **Choose file / Choose folder buttons** — opens a native macOS file or folder picker. The picker runs on the server side via AppleScript (`osascript`), so it returns the real filesystem path and the viewer can tail it live. (Falls back to a `tkinter` dialog on non-macOS platforms.)
720
+ - **Drag and drop** — drop a `.jsonl` file onto the drop zone. The browser reads the file contents and posts them to the server, which saves a copy to `~/.traceact/imports/` and adds it as a source. Because the server holds a copy (not the original), this is a **static snapshot** — new writes to the original file won't appear. The viewer labels imported sources accordingly.
721
+ - **Type a path** — a collapsible text input for pasting or typing an absolute path. This gives live tailing just like the command-line argument.
722
+
723
+ ### macOS launcher (`launch.command`)
724
+
725
+ `launch.command` is a double-clickable shell script in the repo root. Opening it from Finder:
726
+
727
+ 1. Probes port 8765 — if a viewer is already running, opens it immediately and exits.
728
+ 2. Finds Python 3.9+ on the machine (checks pyenv shims, Homebrew, system Python).
729
+ 3. Creates (or reuses) a `.venv/` virtual environment in the same directory.
730
+ 4. Installs or upgrades `traceact` inside that venv.
731
+ 5. Runs `traceact view`, waits for the server to be ready, then opens the browser.
732
+
733
+ The Terminal window stays open so Ctrl+C stops the viewer cleanly. To pass a source file at launch from a script: `open launch.command path/to/traces.jsonl`.
734
+
735
+ ### What the viewer shows
736
+
737
+ - **Trace log** — a live, newest-first table of traces (time, action, status, duration, and touch/error/budget counts). A search box filters by action, kind, status, or touched target. The row count is capped (25 / 50 / 100 / 250, default 100) and paired with live tailing, so the newest traces are always in view.
738
+ - **Trace inspector** — selecting a trace shows its own ID, its parent and root trace IDs (when it is a child trace), kind, duration, and touch/error counts. "Copy JSON" copies the full record.
739
+ - **Trace map** — a visual of one trace: the action as origin, its events and resources as connected nodes, with per-node status and a red marker on failures. Plays as a sequential step-through replay, with a speed slider (1×–10×, live, persisted) and pause/play.
740
+ - **Settings** — accent colour, display density, default trace view, row count, and default replay speed — all persisted to `localStorage`.
741
+
742
+ ### Viewer server API
743
+
744
+ These endpoints are available while a viewer is running. Apps and scripts can call them directly.
745
+
746
+ | Method | Path | Request | Response |
747
+ |---|---|---|---|
748
+ | `GET` | `/api/health` | — | `{"status":"ok","version":"0.2.1","sources":N}` |
749
+ | `GET` | `/api/sources` | — | `[{"name":"...","path":"..."}]` |
750
+ | `POST` | `/api/sources` | `{"path":"..."}` | `{"name":"...","path":"..."}` |
751
+ | `GET` | `/api/pick?type=file\|folder` | — | `{"path":"...","cancelled":bool}` |
752
+ | `POST` | `/api/import` | `{"name":"file.jsonl","content":"..."}` | `{"name":"...","path":"...","imported":true}` |
753
+ | `GET` | `/api/stream?source=NAME&limit=N` | — | SSE stream: `snapshot` then `append` events |
754
+
755
+ The SSE stream delivers one `snapshot` message (the last N traces as a JSON array) then `append` messages for each newly-written trace. A `": keepalive"` comment is sent every 0.5 s when nothing new arrives, to keep the connection alive through proxies.
756
+
757
+ ### Notes
758
+
759
+ - The viewer is a **local, single-node development tool**. It reads files on the machine it runs on. Exposing one machine's traces to another over the network (`traceact serve`) is planned for a later version.
760
+ - The viewer server binds to localhost by default. Only pass `--host 0.0.0.0` if you understand that it exposes trace data (which may contain sensitive payloads) to your network.
761
+
762
+ ---
763
+
764
+ ## Integrating the viewer into your app
765
+
766
+ If your app has its own web UI, you can add a "traceact viewer" button that opens the viewer in a new tab — starting it automatically if it isn't already running.
767
+
768
+ ### Backend route
769
+
770
+ Add a route that calls `launch_or_connect()`. It checks for a running viewer (via `~/.traceact/viewer.json` + health probe), adds your trace source to it, and returns the URL. If no viewer is running it spawns one as a background subprocess and waits up to 3 seconds for it to be ready.
771
+
772
+ **FastAPI** (`launch_or_connect` does blocking I/O, so run it in a thread pool):
773
+ ```python
774
+ import asyncio
775
+ from traceact.viewer.instance import launch_or_connect
776
+
777
+ @router.get("/api/launch-viewer")
778
+ async def launch_viewer():
779
+ loop = asyncio.get_event_loop()
780
+ url = await loop.run_in_executor(None, launch_or_connect,
781
+ "data/traces/traces.jsonl")
782
+ return {"url": url}
783
+ ```
784
+
785
+ **Flask:**
786
+ ```python
787
+ from traceact.viewer.instance import launch_or_connect
788
+ from flask import jsonify
789
+
790
+ @app.get("/api/launch-viewer")
791
+ def launch_viewer():
792
+ url = launch_or_connect(source="data/traces/traces.jsonl")
793
+ return jsonify({"url": url})
794
+ ```
795
+
796
+ `launch_or_connect` signature:
797
+
798
+ ```python
799
+ launch_or_connect(
800
+ source=None, # path to .jsonl file or folder; added to running viewer if given
801
+ host="127.0.0.1", # host to bind if starting a new viewer
802
+ port=8765, # port to try if starting a new viewer
803
+ open_browser=False,
804
+ timeout=3.0, # seconds to wait for a freshly started server to be ready
805
+ ) -> str # returns the viewer URL, e.g. "http://127.0.0.1:8765/"
806
+ ```
807
+
808
+ ### Frontend button
809
+
810
+ The button should be a `<button>` (not an `<a href>`), so you can disable it and show a loading state while the backend starts the viewer:
811
+
812
+ ```javascript
813
+ document.getElementById("btn-traceact-viewer").addEventListener("click", async () => {
814
+ const btn = document.getElementById("btn-traceact-viewer");
815
+ btn.disabled = true;
816
+ btn.textContent = "opening…";
817
+ try {
818
+ const { url } = await fetch("/api/launch-viewer").then(r => r.json());
819
+ window.open(url, "_blank", "noopener");
820
+ } catch {
821
+ window.open("http://127.0.0.1:8765/", "_blank", "noopener");
822
+ } finally {
823
+ btn.disabled = false;
824
+ btn.textContent = "traceact viewer";
825
+ }
826
+ });
827
+ ```
828
+
829
+ The fallback `window.open` in the `catch` block handles the case where the backend route doesn't exist yet (e.g. during development), so the button always does something useful.
830
+
831
+ ### Direct API access (no backend route)
832
+
833
+ If adding a backend route isn't practical, the frontend can probe the viewer directly:
834
+
835
+ ```javascript
836
+ async function openViewer() {
837
+ try {
838
+ await fetch("http://127.0.0.1:8765/api/health", { mode: "no-cors" });
839
+ } catch { /* not running — tell the user to start it */ }
840
+ window.open("http://127.0.0.1:8765/", "_blank", "noopener");
841
+ }
842
+ ```
843
+
844
+ Note: cross-origin `fetch` to the viewer will always be blocked by CORS unless the viewer adds an `Access-Control-Allow-Origin` header (it doesn't, by design). Use `mode: "no-cors"` only to warm up the connection; don't try to read the response. The backend-route approach above is cleaner because it doesn't depend on the client being on the same machine as the viewer.
845
+
846
+ ---
847
+
622
848
  ## Quick reference
623
849
 
624
850
  ```python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "traceact"
7
- version = "0.1.1"
7
+ version = "0.2.1"
8
8
  description = "X-ray vision for your code. Lightweight action-level tracing for Python."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -41,9 +41,19 @@ dev = [
41
41
  Homepage = "https://github.com/traceact/traceact"
42
42
  Repository = "https://github.com/traceact/traceact"
43
43
  Documentation = "https://github.com/traceact/traceact/blob/main/USAGE.md"
44
+ Changelog = "https://github.com/traceact/traceact/blob/main/CHANGELOG.md"
44
45
  Issues = "https://github.com/traceact/traceact/issues"
45
46
  Author = "https://mohammedshehu.com"
46
47
 
48
+ # The `traceact` command line. Both `traceact view` and `traceact show` are
49
+ # handled inside cli:main.
50
+ [project.scripts]
51
+ traceact = "traceact.viewer.cli:main"
52
+
47
53
  [tool.setuptools.packages.find]
48
54
  where = ["."]
49
55
  include = ["traceact*"]
56
+
57
+ # Ship the viewer's static frontend (HTML/CSS/JS) inside the wheel.
58
+ [tool.setuptools.package-data]
59
+ "traceact.viewer" = ["static/*"]
@@ -25,6 +25,8 @@
25
25
  #
26
26
  # ConsoleSink — prints traces to stdout.
27
27
 
28
+ __version__ = "0.2.1"
29
+
28
30
  from traceact.config import configure, reset_config, TraceConfig
29
31
  from traceact.budget import TraceBudget
30
32
  from traceact.trace import ActionTrace
@@ -32,6 +34,7 @@ from traceact.decorators import traced_action
32
34
  from traceact.sinks import JsonlSink, ConsoleSink
33
35
 
34
36
  __all__ = [
37
+ "__version__",
35
38
  "ActionTrace",
36
39
  "TraceConfig",
37
40
  "TraceBudget",
@@ -84,6 +84,39 @@ class TraceBudget:
84
84
  self.sample_rate = sample_rate
85
85
  self.always_trace_errors = always_trace_errors
86
86
 
87
+ @classmethod
88
+ def production(cls) -> "TraceBudget":
89
+ """
90
+ A preset budget tuned for high-volume production use.
91
+
92
+ The package default records every trace (sample_rate=1.0), which is the
93
+ right behaviour for development and first-run: you trace a function, run
94
+ it once, and the trace is there. That default is deliberately NOT changed
95
+ globally, because a developer who wired TraceAct in and then found nothing
96
+ recorded would be justifiably confused.
97
+
98
+ In production, though, an app might run hundreds or thousands of traced
99
+ actions per second. Recording all of them can strain the sink and the
100
+ viewer. This preset opts into a lighter footprint:
101
+
102
+ sample_rate=0.1 record roughly 10% of successful traces
103
+ always_trace_errors=True never drop a failed trace, even when sampled
104
+
105
+ So you keep a representative sample of healthy traffic while still
106
+ capturing every failure — which is the part you most need when debugging.
107
+
108
+ Only sample_rate and always_trace_errors are set here. Every other field
109
+ is left as None so it inherits from the package default or a parent trace.
110
+
111
+ Usage:
112
+ configure(budget=TraceBudget.production())
113
+
114
+ # or override a single field on top of the preset:
115
+ budget = TraceBudget.production()
116
+ budget.sample_rate = 0.25 # record 25% instead of 10%
117
+ """
118
+ return cls(sample_rate=0.1, always_trace_errors=True)
119
+
87
120
 
88
121
  # ---------------------------------------------------------------------------
89
122
  # Package-level defaults