zeno-cli 0.3.4__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 (72) hide show
  1. zeno_cli-0.3.4/.gitignore +46 -0
  2. zeno_cli-0.3.4/PKG-INFO +161 -0
  3. zeno_cli-0.3.4/README.md +134 -0
  4. zeno_cli-0.3.4/_vendor_build/zeno_adapters/__init__.py +17 -0
  5. zeno_cli-0.3.4/_vendor_build/zeno_adapters/_common.py +38 -0
  6. zeno_cli-0.3.4/_vendor_build/zeno_adapters/anthropic.py +68 -0
  7. zeno_cli-0.3.4/_vendor_build/zeno_adapters/claude_code.py +101 -0
  8. zeno_cli-0.3.4/_vendor_build/zeno_adapters/crewai.py +92 -0
  9. zeno_cli-0.3.4/_vendor_build/zeno_adapters/langgraph.py +49 -0
  10. zeno_cli-0.3.4/_vendor_build/zeno_adapters/openai.py +108 -0
  11. zeno_cli-0.3.4/_vendor_build/zeno_core/__init__.py +67 -0
  12. zeno_cli-0.3.4/_vendor_build/zeno_core/analytics.py +193 -0
  13. zeno_cli-0.3.4/_vendor_build/zeno_core/rtlx_s.py +460 -0
  14. zeno_cli-0.3.4/_vendor_build/zeno_core/streak.py +178 -0
  15. zeno_cli-0.3.4/_vendor_build/zeno_core/tlx_s.py +192 -0
  16. zeno_cli-0.3.4/_vendor_build/zeno_sdk/__init__.py +6 -0
  17. zeno_cli-0.3.4/_vendor_build/zeno_sdk/_generated/__init__.py +6 -0
  18. zeno_cli-0.3.4/_vendor_build/zeno_sdk/_generated/client.py +819 -0
  19. zeno_cli-0.3.4/_vendor_build/zeno_sdk/_migrations/alembic/env.py +33 -0
  20. zeno_cli-0.3.4/_vendor_build/zeno_sdk/_migrations/alembic/script.py.mako +18 -0
  21. zeno_cli-0.3.4/_vendor_build/zeno_sdk/_migrations/alembic/versions/0001_initial.py +79 -0
  22. zeno_cli-0.3.4/_vendor_build/zeno_sdk/_migrations/alembic/versions/0002_cognition_samples.py +53 -0
  23. zeno_cli-0.3.4/_vendor_build/zeno_sdk/_migrations/alembic/versions/0003_cognition_drivers.py +41 -0
  24. zeno_cli-0.3.4/_vendor_build/zeno_sdk/_migrations/alembic/versions/0004_transcript_intelligence.py +248 -0
  25. zeno_cli-0.3.4/_vendor_build/zeno_sdk/_migrations/alembic.ini +35 -0
  26. zeno_cli-0.3.4/_vendor_build/zeno_sdk/_runtime.py +12 -0
  27. zeno_cli-0.3.4/_vendor_build/zeno_sdk/adapters/__init__.py +15 -0
  28. zeno_cli-0.3.4/_vendor_build/zeno_sdk/adapters/anthropic.py +5 -0
  29. zeno_cli-0.3.4/_vendor_build/zeno_sdk/adapters/claude_code.py +5 -0
  30. zeno_cli-0.3.4/_vendor_build/zeno_sdk/adapters/crewai.py +5 -0
  31. zeno_cli-0.3.4/_vendor_build/zeno_sdk/adapters/langgraph.py +5 -0
  32. zeno_cli-0.3.4/_vendor_build/zeno_sdk/adapters/openai.py +5 -0
  33. zeno_cli-0.3.4/_vendor_build/zeno_sdk/auth.py +25 -0
  34. zeno_cli-0.3.4/_vendor_build/zeno_sdk/client.py +87 -0
  35. zeno_cli-0.3.4/_vendor_build/zeno_sdk/config.py +61 -0
  36. zeno_cli-0.3.4/_vendor_build/zeno_sdk/daemon.py +72 -0
  37. zeno_cli-0.3.4/_vendor_build/zeno_sdk/privacy.py +46 -0
  38. zeno_cli-0.3.4/_vendor_build/zeno_sdk/session.py +179 -0
  39. zeno_cli-0.3.4/_vendor_build/zeno_sdk/storage.py +487 -0
  40. zeno_cli-0.3.4/_vendor_build/zeno_sdk/types/__init__.py +121 -0
  41. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/__init__.py +19 -0
  42. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/analytics.py +588 -0
  43. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/compression.py +123 -0
  44. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/ingest.py +376 -0
  45. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/model.py +129 -0
  46. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/parsers/__init__.py +31 -0
  47. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/parsers/claude_code.py +169 -0
  48. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/parsers/codex.py +265 -0
  49. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/parsers/cursor.py +198 -0
  50. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/prices.py +281 -0
  51. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/schema.py +277 -0
  52. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/signals.py +319 -0
  53. zeno_cli-0.3.4/_vendor_build/zeno_session_intel/taxonomy.py +71 -0
  54. zeno_cli-0.3.4/data/outreach_suppression.txt +14 -0
  55. zeno_cli-0.3.4/hatch_build.py +155 -0
  56. zeno_cli-0.3.4/pyproject.toml +87 -0
  57. zeno_cli-0.3.4/scripts/install-smoke.sh +93 -0
  58. zeno_cli-0.3.4/src/zeno_cli/__init__.py +1 -0
  59. zeno_cli-0.3.4/src/zeno_cli/_hooks/cc_bridge.py +1016 -0
  60. zeno_cli-0.3.4/src/zeno_cli/doctor.py +535 -0
  61. zeno_cli-0.3.4/src/zeno_cli/hook_install.py +269 -0
  62. zeno_cli-0.3.4/src/zeno_cli/hud/__init__.py +1 -0
  63. zeno_cli-0.3.4/src/zeno_cli/hud/hud_install.py +652 -0
  64. zeno_cli-0.3.4/src/zeno_cli/hud/zeno_attention.py +288 -0
  65. zeno_cli-0.3.4/src/zeno_cli/hud/zeno_cognition.py +457 -0
  66. zeno_cli-0.3.4/src/zeno_cli/hud/zeno_hud.py +496 -0
  67. zeno_cli-0.3.4/src/zeno_cli/interview_invites.py +342 -0
  68. zeno_cli-0.3.4/src/zeno_cli/login.py +241 -0
  69. zeno_cli-0.3.4/src/zeno_cli/main.py +2534 -0
  70. zeno_cli-0.3.4/src/zeno_cli/onboard.py +206 -0
  71. zeno_cli-0.3.4/src/zeno_cli/outreach.py +456 -0
  72. zeno_cli-0.3.4/src/zeno_cli/version.py +67 -0
@@ -0,0 +1,46 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .venv/
7
+ .coverage
8
+ .coverage.*
9
+ htmlcov/
10
+
11
+ # zeno-cli build-time vendor stage (apps/cli/hatch_build.py writes here)
12
+ _vendor_build/
13
+ _vendor_build.tmp/
14
+
15
+ # Node
16
+ node_modules/
17
+ .pnpm-store/
18
+ dist/
19
+ coverage/
20
+ .turbo/
21
+ .next/
22
+ *.tsbuildinfo
23
+
24
+ # OS / editor
25
+ .DS_Store
26
+ .idea/
27
+ .vscode/
28
+
29
+ # Env / secrets
30
+ .env
31
+ .env.*
32
+ !.env.example
33
+
34
+ # Local app state
35
+ .zeno/
36
+
37
+ # Local SQLite (dev / demo capture)
38
+ *.db
39
+ *.db-journal
40
+ *.db-wal
41
+ *.db-shm
42
+
43
+ # Website (apps/website) artifacts
44
+ apps/website/playwright-report/
45
+ apps/website/test-results/
46
+ apps/website/.lighthouseci/
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: zeno-cli
3
+ Version: 0.3.4
4
+ Summary: Zeno CLI - measure the supervision cost of AI-assisted work
5
+ Project-URL: Homepage, https://zeno.center
6
+ Project-URL: Repository, https://github.com/Marwan01/zeno
7
+ Author-email: Mar Helali <mar@1dev.space>
8
+ License-Expression: Apache-2.0
9
+ Keywords: ai,claude-code,cognitive-load,rtlx-s,session-analytics,supervision
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development
16
+ Requires-Python: >=3.12
17
+ Requires-Dist: aiosqlite>=0.20.0
18
+ Requires-Dist: alembic>=1.13.2
19
+ Requires-Dist: keyring>=25.3.0
20
+ Requires-Dist: matplotlib>=3.9.2
21
+ Requires-Dist: numpy>=2.0
22
+ Requires-Dist: pydantic>=2.9.0
23
+ Requires-Dist: rich>=13.8.1
24
+ Requires-Dist: scipy>=1.14.1
25
+ Requires-Dist: textual>=0.86.1
26
+ Description-Content-Type: text/markdown
27
+
28
+ # zeno-cli
29
+
30
+ Measure the supervision cost of AI-assisted work, from your terminal. `zeno`
31
+ records RTLX-S load probes + session activity, fits your personal "babysitting
32
+ tax" curve, and surfaces where your optimal number of parallel agents sits.
33
+
34
+ ## Install
35
+
36
+ Install the `zeno` CLI straight from the monorepo over SSH. It is one
37
+ self-contained package: the SDK, core, adapters, session-intelligence engine,
38
+ and SQLite migrations all ship inside it. Requires **Python >= 3.12** and GitHub
39
+ SSH access to the repo.
40
+
41
+ ```sh
42
+ uv tool install \
43
+ --from "git+ssh://git@github.com/Marwan01/zeno.git@cli-v0.3.3#subdirectory=apps/cli" \
44
+ zeno-cli
45
+ ```
46
+
47
+ `pipx install "git+ssh://git@github.com/Marwan01/zeno.git@cli-v0.3.3#subdirectory=apps/cli"`
48
+ works the same way. The git clone runs an in-tree build, so the hatchling build
49
+ hook (`apps/cli/hatch_build.py`) vendors the workspace packages into the wheel.
50
+ **Update** by bumping the tag: re-run with `@cli-v0.3.3` (uv) or
51
+ `pipx install --force ...@<newtag>`.
52
+
53
+ **Why git+ssh and not an index:** TestPyPI is not a durable distribution channel
54
+ and a private index is overkill at team scale. A public PyPI wheel comes only
55
+ when non-repo-access users do. The packaging blocker for it is now resolved - the
56
+ build is sdist-clean (`uv build --sdist` -> a wheel built from that sdist installs
57
+ and runs every verb; see the build-hook note under [Packaging](#packaging-notes)),
58
+ so the remaining gate is purely "do public users exist yet", not a broken build.
59
+
60
+ ### Install channels (staged)
61
+
62
+ The distribution channel widens as the audience does. Use the one that matches
63
+ where the project is, not the most convenient:
64
+
65
+ | Stage | Channel | Command | Status |
66
+ | :--- | :--- | :--- | :--- |
67
+ | Now | git+ssh tag | `uv tool install --from "git+ssh://git@github.com/Marwan01/zeno.git@cli-v0.3.3#subdirectory=apps/cli" zeno-cli` | Live (needs repo SSH access) |
68
+ | Next | PyPI | `uv tool install zeno-cli` (or `pipx install zeno-cli`) | Lands when `.github/workflows/publish.yml` runs on the first GitHub release; the packaging metadata is ready |
69
+ | Later | Homebrew | `brew install zeno-cli` | Formula not written yet |
70
+
71
+ The PyPI publish is OIDC Trusted-Publishing gated and fires only on a published
72
+ GitHub release - see `.github/workflows/publish.yml`. Until that first release,
73
+ the git+ssh tag is the supported path.
74
+
75
+ **From a built wheel** (offline / air-gapped; the wheel is fully self-contained):
76
+
77
+ ```sh
78
+ uv build --wheel --package zeno-cli # -> dist/zeno_cli-*.whl
79
+ uv tool install ./dist/zeno_cli-0.3.3-py3-none-any.whl
80
+ ```
81
+
82
+ **From source** (development inside the monorepo, no install):
83
+
84
+ ```sh
85
+ uv run --project /path/to/zeno zeno <cmd>
86
+ ```
87
+
88
+ New here? Follow `docs/ADOPT.md` for the 10-minute install -> capture -> survey loop.
89
+
90
+ ## Use
91
+
92
+ ```sh
93
+ zeno survey # log how a session felt (~6s, RTLX-S 5-item probe)
94
+ zeno status # tier, streak, tax curve, cognitive state, today's survey
95
+ zeno curve # render the babysitting-tax curve (needs ~10 sessions of data)
96
+ zeno weekly # 7-day rollup
97
+ zeno doctor # health check: API reachability + identity
98
+ zeno login # browser sign-in; stores a Clerk JWT in the OS keyring
99
+ zeno whoami # show the identity + tier the API sees for your token
100
+ zeno logout # clear the stored token
101
+ ```
102
+
103
+ ### Authentication
104
+
105
+ `zeno login` runs a browser + loopback sign-in and stores the resulting Clerk
106
+ JWT in your OS keyring; the CLI sends it as a `Bearer` token so the API
107
+ attributes requests to your user. Token resolution order, used by every
108
+ command: `ZENO_API_TOKEN` env override first, then the keyring token from
109
+ `zeno login`, then none (local-only). Use `zeno login --no-browser` for SSH /
110
+ headless (prints the URL), and `--authorize-url` / `ZENO_LOGIN_AUTHORIZE_URL`
111
+ to target a non-default bridge.
112
+
113
+ > The dashboard `/cli/authorize` bridge page + the Clerk `zeno-api` JWT template
114
+ > that mint the token are a documented follow-up (not yet shipped). See
115
+ > `src/zeno_cli/login.py` for the callback contract.
116
+
117
+ Full daily-loop guide: `docs/CLI_QUICKSTART.md`. Requires Python >= 3.12.
118
+
119
+ ## Cognition HUD add-on
120
+
121
+ `zeno-hud-bar` is the single differentiated cognition line (`att / eff / drv`) that
122
+ stacks UNDER whatever popular HUD you already run, instead of replacing it. Claude Code
123
+ has one statusLine slot, so zeno rides the HUD you have:
124
+
125
+ ```sh
126
+ zeno hud install # auto-detect: ccstatusline (native widget) or claude-hud (wrapper)
127
+ zeno hud install --dry-run # print what would change, write nothing
128
+ zeno hud status # what is detected + whether the line is wired in
129
+ zeno hud uninstall # remove from both adapters (idempotent; --restore for byte-for-byte)
130
+ ```
131
+
132
+ `--target {auto,ccstatusline,claude-hud}` forces an adapter (auto prefers ccstatusline
133
+ when both are present). Every edit is backed up and reversible. The bar is crash-safe: on
134
+ any error it prints an empty line and exits 0, so it never breaks the host HUD. Full model
135
+ and both recipes: `docs/HUD_ADDON.md`.
136
+
137
+ ## Packaging notes
138
+
139
+ - **sdist-clean via a build hook.** `apps/cli/hatch_build.py` (a custom hatchling
140
+ build hook, enabled on BOTH the `wheel` and `sdist` targets) vendors the four
141
+ workspace packages (`zeno_core` / `zeno_sdk` / `zeno_adapters` /
142
+ `zeno_session_intel`) plus the SDK alembic tree into an in-root, gitignored
143
+ `_vendor_build/` stage at build time. The wheel target flattens them to the
144
+ archive top level (the exact layout the old static `force-include` produced, so
145
+ absolute imports resolve unchanged); the sdist target ships the whole
146
+ `_vendor_build/` stage inside the tarball so a wheel built FROM the sdist finds
147
+ the sources locally. This replaced the static `[tool.hatch...force-include]` of
148
+ `../../packages/...` relative-parent paths, whose `..` escaped the sdist root and
149
+ were silently dropped - a wheel from that sdist died with "Forced include not
150
+ found". Repo source under `packages/*` is never moved, so the uv workspace, the
151
+ API, and the test suite keep importing the editable members unchanged; the
152
+ vendored copy is purely a build artifact.
153
+ - `zeno ingest` / `zeno usage` are backed by `zeno_session_intel`, vendored
154
+ alongside the other internal packages by the build hook. It is stdlib-only with
155
+ relative-only internal imports, so it flattens into the wheel with zero import
156
+ rewrites. Omit it and both subcommands abort with a "not bundled" hint at runtime
157
+ (the regression fixed in 0.3.3); `scripts/install-smoke.sh` now exercises every
158
+ advertised subcommand so this cannot ship silently again.
159
+ - Agent-framework adapters (`zeno_sdk.adapters` -> crewai / openai / anthropic /
160
+ langgraph) are off the CLI path and pull heavy framework deps; they are kept
161
+ optional and will be exposed as extras (e.g. `zeno-cli[crewai]`) in a later stage.
@@ -0,0 +1,134 @@
1
+ # zeno-cli
2
+
3
+ Measure the supervision cost of AI-assisted work, from your terminal. `zeno`
4
+ records RTLX-S load probes + session activity, fits your personal "babysitting
5
+ tax" curve, and surfaces where your optimal number of parallel agents sits.
6
+
7
+ ## Install
8
+
9
+ Install the `zeno` CLI straight from the monorepo over SSH. It is one
10
+ self-contained package: the SDK, core, adapters, session-intelligence engine,
11
+ and SQLite migrations all ship inside it. Requires **Python >= 3.12** and GitHub
12
+ SSH access to the repo.
13
+
14
+ ```sh
15
+ uv tool install \
16
+ --from "git+ssh://git@github.com/Marwan01/zeno.git@cli-v0.3.3#subdirectory=apps/cli" \
17
+ zeno-cli
18
+ ```
19
+
20
+ `pipx install "git+ssh://git@github.com/Marwan01/zeno.git@cli-v0.3.3#subdirectory=apps/cli"`
21
+ works the same way. The git clone runs an in-tree build, so the hatchling build
22
+ hook (`apps/cli/hatch_build.py`) vendors the workspace packages into the wheel.
23
+ **Update** by bumping the tag: re-run with `@cli-v0.3.3` (uv) or
24
+ `pipx install --force ...@<newtag>`.
25
+
26
+ **Why git+ssh and not an index:** TestPyPI is not a durable distribution channel
27
+ and a private index is overkill at team scale. A public PyPI wheel comes only
28
+ when non-repo-access users do. The packaging blocker for it is now resolved - the
29
+ build is sdist-clean (`uv build --sdist` -> a wheel built from that sdist installs
30
+ and runs every verb; see the build-hook note under [Packaging](#packaging-notes)),
31
+ so the remaining gate is purely "do public users exist yet", not a broken build.
32
+
33
+ ### Install channels (staged)
34
+
35
+ The distribution channel widens as the audience does. Use the one that matches
36
+ where the project is, not the most convenient:
37
+
38
+ | Stage | Channel | Command | Status |
39
+ | :--- | :--- | :--- | :--- |
40
+ | Now | git+ssh tag | `uv tool install --from "git+ssh://git@github.com/Marwan01/zeno.git@cli-v0.3.3#subdirectory=apps/cli" zeno-cli` | Live (needs repo SSH access) |
41
+ | Next | PyPI | `uv tool install zeno-cli` (or `pipx install zeno-cli`) | Lands when `.github/workflows/publish.yml` runs on the first GitHub release; the packaging metadata is ready |
42
+ | Later | Homebrew | `brew install zeno-cli` | Formula not written yet |
43
+
44
+ The PyPI publish is OIDC Trusted-Publishing gated and fires only on a published
45
+ GitHub release - see `.github/workflows/publish.yml`. Until that first release,
46
+ the git+ssh tag is the supported path.
47
+
48
+ **From a built wheel** (offline / air-gapped; the wheel is fully self-contained):
49
+
50
+ ```sh
51
+ uv build --wheel --package zeno-cli # -> dist/zeno_cli-*.whl
52
+ uv tool install ./dist/zeno_cli-0.3.3-py3-none-any.whl
53
+ ```
54
+
55
+ **From source** (development inside the monorepo, no install):
56
+
57
+ ```sh
58
+ uv run --project /path/to/zeno zeno <cmd>
59
+ ```
60
+
61
+ New here? Follow `docs/ADOPT.md` for the 10-minute install -> capture -> survey loop.
62
+
63
+ ## Use
64
+
65
+ ```sh
66
+ zeno survey # log how a session felt (~6s, RTLX-S 5-item probe)
67
+ zeno status # tier, streak, tax curve, cognitive state, today's survey
68
+ zeno curve # render the babysitting-tax curve (needs ~10 sessions of data)
69
+ zeno weekly # 7-day rollup
70
+ zeno doctor # health check: API reachability + identity
71
+ zeno login # browser sign-in; stores a Clerk JWT in the OS keyring
72
+ zeno whoami # show the identity + tier the API sees for your token
73
+ zeno logout # clear the stored token
74
+ ```
75
+
76
+ ### Authentication
77
+
78
+ `zeno login` runs a browser + loopback sign-in and stores the resulting Clerk
79
+ JWT in your OS keyring; the CLI sends it as a `Bearer` token so the API
80
+ attributes requests to your user. Token resolution order, used by every
81
+ command: `ZENO_API_TOKEN` env override first, then the keyring token from
82
+ `zeno login`, then none (local-only). Use `zeno login --no-browser` for SSH /
83
+ headless (prints the URL), and `--authorize-url` / `ZENO_LOGIN_AUTHORIZE_URL`
84
+ to target a non-default bridge.
85
+
86
+ > The dashboard `/cli/authorize` bridge page + the Clerk `zeno-api` JWT template
87
+ > that mint the token are a documented follow-up (not yet shipped). See
88
+ > `src/zeno_cli/login.py` for the callback contract.
89
+
90
+ Full daily-loop guide: `docs/CLI_QUICKSTART.md`. Requires Python >= 3.12.
91
+
92
+ ## Cognition HUD add-on
93
+
94
+ `zeno-hud-bar` is the single differentiated cognition line (`att / eff / drv`) that
95
+ stacks UNDER whatever popular HUD you already run, instead of replacing it. Claude Code
96
+ has one statusLine slot, so zeno rides the HUD you have:
97
+
98
+ ```sh
99
+ zeno hud install # auto-detect: ccstatusline (native widget) or claude-hud (wrapper)
100
+ zeno hud install --dry-run # print what would change, write nothing
101
+ zeno hud status # what is detected + whether the line is wired in
102
+ zeno hud uninstall # remove from both adapters (idempotent; --restore for byte-for-byte)
103
+ ```
104
+
105
+ `--target {auto,ccstatusline,claude-hud}` forces an adapter (auto prefers ccstatusline
106
+ when both are present). Every edit is backed up and reversible. The bar is crash-safe: on
107
+ any error it prints an empty line and exits 0, so it never breaks the host HUD. Full model
108
+ and both recipes: `docs/HUD_ADDON.md`.
109
+
110
+ ## Packaging notes
111
+
112
+ - **sdist-clean via a build hook.** `apps/cli/hatch_build.py` (a custom hatchling
113
+ build hook, enabled on BOTH the `wheel` and `sdist` targets) vendors the four
114
+ workspace packages (`zeno_core` / `zeno_sdk` / `zeno_adapters` /
115
+ `zeno_session_intel`) plus the SDK alembic tree into an in-root, gitignored
116
+ `_vendor_build/` stage at build time. The wheel target flattens them to the
117
+ archive top level (the exact layout the old static `force-include` produced, so
118
+ absolute imports resolve unchanged); the sdist target ships the whole
119
+ `_vendor_build/` stage inside the tarball so a wheel built FROM the sdist finds
120
+ the sources locally. This replaced the static `[tool.hatch...force-include]` of
121
+ `../../packages/...` relative-parent paths, whose `..` escaped the sdist root and
122
+ were silently dropped - a wheel from that sdist died with "Forced include not
123
+ found". Repo source under `packages/*` is never moved, so the uv workspace, the
124
+ API, and the test suite keep importing the editable members unchanged; the
125
+ vendored copy is purely a build artifact.
126
+ - `zeno ingest` / `zeno usage` are backed by `zeno_session_intel`, vendored
127
+ alongside the other internal packages by the build hook. It is stdlib-only with
128
+ relative-only internal imports, so it flattens into the wheel with zero import
129
+ rewrites. Omit it and both subcommands abort with a "not bundled" hint at runtime
130
+ (the regression fixed in 0.3.3); `scripts/install-smoke.sh` now exercises every
131
+ advertised subcommand so this cannot ship silently again.
132
+ - Agent-framework adapters (`zeno_sdk.adapters` -> crewai / openai / anthropic /
133
+ langgraph) are off the CLI path and pull heavy framework deps; they are kept
134
+ optional and will be exposed as extras (e.g. `zeno-cli[crewai]`) in a later stage.
@@ -0,0 +1,17 @@
1
+ """Adapter package exports."""
2
+
3
+ from .anthropic import instrument as instrument_anthropic
4
+ from .claude_code import instrument as instrument_claude_code
5
+ from .crewai import instrument_agent as instrument_crewai_agent
6
+ from .crewai import instrument_crew as instrument_crewai_crew
7
+ from .langgraph import instrument as instrument_langgraph
8
+ from .openai import instrument as instrument_openai
9
+
10
+ __all__ = [
11
+ "instrument_anthropic",
12
+ "instrument_claude_code",
13
+ "instrument_crewai_agent",
14
+ "instrument_crewai_crew",
15
+ "instrument_langgraph",
16
+ "instrument_openai",
17
+ ]
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from collections.abc import Callable
5
+ from typing import Any
6
+
7
+
8
+ def _get(obj: Any, key: str, default: Any = None) -> Any:
9
+ if isinstance(obj, dict):
10
+ return obj.get(key, default)
11
+ return getattr(obj, key, default)
12
+
13
+
14
+ def _tool_names_from_anthropic(result: Any) -> list[str]:
15
+ content = _get(result, "content", [])
16
+ names: list[str] = []
17
+ for item in content or []:
18
+ if _get(item, "type") == "tool_use":
19
+ name = _get(item, "name")
20
+ if isinstance(name, str):
21
+ names.append(name)
22
+ return names
23
+
24
+
25
+ def _extract_usage(result: Any) -> tuple[int | None, int | None]:
26
+ usage = _get(result, "usage")
27
+ if usage is None:
28
+ return (None, None)
29
+ input_tokens = _get(usage, "input_tokens")
30
+ output_tokens = _get(usage, "output_tokens")
31
+ return (input_tokens, output_tokens)
32
+
33
+
34
+ def call_with_timing(fn: Callable[..., Any], *args: Any, **kwargs: Any) -> tuple[Any, int]:
35
+ started = time.perf_counter_ns()
36
+ result = fn(*args, **kwargs)
37
+ latency_ms = int((time.perf_counter_ns() - started) / 1_000_000)
38
+ return result, max(latency_ms, 0)
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from ._common import _extract_usage, _tool_names_from_anthropic, call_with_timing
7
+
8
+
9
+ @dataclass(slots=True)
10
+ class _MessagesProxy:
11
+ inner: Any
12
+ zeno_session: Any
13
+ record_content: bool = False
14
+
15
+ def create(self, *args: Any, **kwargs: Any) -> Any:
16
+ model = kwargs.get("model", "unknown")
17
+ with self.zeno_session.agent_run(model=model) as run:
18
+ try:
19
+ result, latency_ms = call_with_timing(self.inner.create, *args, **kwargs)
20
+ input_tokens, output_tokens = _extract_usage(result)
21
+ metadata: dict[str, Any] = {
22
+ "provider": "anthropic",
23
+ "harness": "anthropic",
24
+ "input_tokens": input_tokens,
25
+ "output_tokens": output_tokens,
26
+ "tool_names": _tool_names_from_anthropic(result),
27
+ "tool_call_count": len(_tool_names_from_anthropic(result)),
28
+ }
29
+ if self.record_content:
30
+ metadata["prompt"] = str(kwargs.get("messages"))
31
+ metadata["output"] = str(getattr(result, "content", None))
32
+
33
+ self.zeno_session.record(
34
+ type="accept",
35
+ latency_ms_to_decide=latency_ms,
36
+ metadata=metadata,
37
+ )
38
+ run.outcome("accepted")
39
+ return result
40
+ except Exception:
41
+ self.zeno_session.record(
42
+ type="reject",
43
+ metadata={"provider": "anthropic", "harness": "anthropic"},
44
+ )
45
+ run.outcome("rejected")
46
+ raise
47
+
48
+
49
+ @dataclass(slots=True)
50
+ class _ClientProxy:
51
+ client: Any
52
+ zeno_session: Any
53
+ record_content: bool = False
54
+
55
+ @property
56
+ def messages(self) -> _MessagesProxy:
57
+ return _MessagesProxy(
58
+ inner=self.client.messages,
59
+ zeno_session=self.zeno_session,
60
+ record_content=self.record_content,
61
+ )
62
+
63
+ def __getattr__(self, item: str) -> Any:
64
+ return getattr(self.client, item)
65
+
66
+
67
+ def instrument(client: Any, *, zeno_session: Any, record_content: bool = False) -> Any:
68
+ return _ClientProxy(client=client, zeno_session=zeno_session, record_content=record_content)
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from ._common import _extract_usage, _tool_names_from_anthropic, call_with_timing
7
+
8
+
9
+ def _trace_metadata(tracer: Any) -> dict[str, Any]:
10
+ if tracer is None:
11
+ return {"trace_event_count": 0, "trace_event_types": []}
12
+ events = tracer.get_recent_events()
13
+ event_types = sorted(
14
+ {
15
+ event.get("type")
16
+ for event in events
17
+ if isinstance(event, dict) and isinstance(event.get("type"), str)
18
+ }
19
+ )
20
+ return {
21
+ "trace_event_count": len(events),
22
+ "trace_event_types": event_types,
23
+ }
24
+
25
+
26
+ @dataclass(slots=True)
27
+ class _MessagesProxy:
28
+ inner: Any
29
+ tracer: Any
30
+ zeno_session: Any
31
+ record_content: bool = False
32
+
33
+ def create(self, *args: Any, **kwargs: Any) -> Any:
34
+ model = kwargs.get("model", "unknown")
35
+ with self.zeno_session.agent_run(model=model) as run:
36
+ try:
37
+ result, latency_ms = call_with_timing(self.inner.create, *args, **kwargs)
38
+ input_tokens, output_tokens = _extract_usage(result)
39
+ tool_names = _tool_names_from_anthropic(result)
40
+ metadata: dict[str, Any] = {
41
+ "provider": "anthropic",
42
+ "harness": "claude-code",
43
+ "input_tokens": input_tokens,
44
+ "output_tokens": output_tokens,
45
+ "tool_names": tool_names,
46
+ "tool_call_count": len(tool_names),
47
+ }
48
+ metadata.update(_trace_metadata(self.tracer))
49
+ if self.record_content:
50
+ metadata["prompt"] = str(kwargs.get("messages"))
51
+ metadata["output"] = str(getattr(result, "content", None))
52
+
53
+ self.zeno_session.record(
54
+ type="accept",
55
+ latency_ms_to_decide=latency_ms,
56
+ metadata=metadata,
57
+ )
58
+ run.outcome("accepted")
59
+ return result
60
+ except Exception:
61
+ self.zeno_session.record(
62
+ type="reject",
63
+ metadata={"provider": "anthropic", "harness": "claude-code"},
64
+ )
65
+ run.outcome("rejected")
66
+ raise
67
+
68
+
69
+ @dataclass(slots=True)
70
+ class _ClientProxy:
71
+ client: Any
72
+ tracer: Any
73
+ zeno_session: Any
74
+ record_content: bool = False
75
+
76
+ @property
77
+ def messages(self) -> _MessagesProxy:
78
+ return _MessagesProxy(
79
+ inner=self.client.messages,
80
+ tracer=self.tracer,
81
+ zeno_session=self.zeno_session,
82
+ record_content=self.record_content,
83
+ )
84
+
85
+ def __getattr__(self, item: str) -> Any:
86
+ return getattr(self.client, item)
87
+
88
+
89
+ def instrument(
90
+ client: Any,
91
+ *,
92
+ tracer: Any,
93
+ zeno_session: Any,
94
+ record_content: bool = False,
95
+ ) -> Any:
96
+ return _ClientProxy(
97
+ client=client,
98
+ tracer=tracer,
99
+ zeno_session=zeno_session,
100
+ record_content=record_content,
101
+ )
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from ._common import call_with_timing
7
+
8
+
9
+ @dataclass(slots=True)
10
+ class InstrumentedAgent:
11
+ inner: Any
12
+ zeno_session: Any
13
+ model: str = "crewai-agent"
14
+ record_content: bool = False
15
+
16
+ def run(self, *args: Any, **kwargs: Any) -> Any:
17
+ with self.zeno_session.agent_run(model=self.model) as run:
18
+ try:
19
+ result, latency_ms = call_with_timing(self.inner.run, *args, **kwargs)
20
+ metadata: dict[str, Any] = {
21
+ "provider": "crewai",
22
+ "harness": "crewai",
23
+ "tool_call_count": int(getattr(result, "tool_call_count", 0)),
24
+ "tool_names": list(getattr(result, "tool_names", [])),
25
+ "status": getattr(result, "status", "success"),
26
+ }
27
+ if self.record_content:
28
+ metadata["prompt"] = str(kwargs.get("task") or kwargs.get("input"))
29
+ metadata["output"] = str(getattr(result, "output", None))
30
+ self.zeno_session.record(
31
+ type="accept",
32
+ latency_ms_to_decide=latency_ms,
33
+ metadata=metadata,
34
+ )
35
+ run.outcome("accepted")
36
+ return result
37
+ except Exception:
38
+ self.zeno_session.record(
39
+ type="reject",
40
+ metadata={"provider": "crewai", "harness": "crewai"},
41
+ )
42
+ run.outcome("rejected")
43
+ raise
44
+
45
+ def __getattr__(self, item: str) -> Any:
46
+ return getattr(self.inner, item)
47
+
48
+
49
+ @dataclass(slots=True)
50
+ class InstrumentedCrew:
51
+ inner: Any
52
+ zeno_session: Any
53
+ model: str = "crewai-crew"
54
+ record_content: bool = False
55
+
56
+ def kickoff(self, *args: Any, **kwargs: Any) -> Any:
57
+ with self.zeno_session.agent_run(model=self.model) as run:
58
+ try:
59
+ result, latency_ms = call_with_timing(self.inner.kickoff, *args, **kwargs)
60
+ metadata: dict[str, Any] = {
61
+ "provider": "crewai",
62
+ "harness": "crewai",
63
+ "status": "kickoff",
64
+ }
65
+ if self.record_content:
66
+ metadata["prompt"] = str(kwargs.get("inputs"))
67
+ metadata["output"] = str(result)
68
+ self.zeno_session.record(
69
+ type="accept",
70
+ latency_ms_to_decide=latency_ms,
71
+ metadata=metadata,
72
+ )
73
+ run.outcome("accepted")
74
+ return result
75
+ except Exception:
76
+ self.zeno_session.record(
77
+ type="reject",
78
+ metadata={"provider": "crewai", "harness": "crewai"},
79
+ )
80
+ run.outcome("rejected")
81
+ raise
82
+
83
+ def __getattr__(self, item: str) -> Any:
84
+ return getattr(self.inner, item)
85
+
86
+
87
+ def instrument_agent(agent: Any, *, zeno_session: Any, record_content: bool = False) -> Any:
88
+ return InstrumentedAgent(inner=agent, zeno_session=zeno_session, record_content=record_content)
89
+
90
+
91
+ def instrument_crew(crew: Any, *, zeno_session: Any, record_content: bool = False) -> Any:
92
+ return InstrumentedCrew(inner=crew, zeno_session=zeno_session, record_content=record_content)