zeno-cli 0.3.6__tar.gz → 0.3.8__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.6 → zeno_cli-0.3.8}/PKG-INFO +1 -1
  2. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/_generated/client.py +14 -0
  3. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/pyproject.toml +1 -1
  4. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/_hooks/cc_bridge.py +30 -12
  5. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/login.py +11 -11
  6. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/.gitignore +0 -0
  7. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/README.md +0 -0
  8. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_adapters/__init__.py +0 -0
  9. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_adapters/_common.py +0 -0
  10. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_adapters/anthropic.py +0 -0
  11. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_adapters/claude_code.py +0 -0
  12. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_adapters/crewai.py +0 -0
  13. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_adapters/langgraph.py +0 -0
  14. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_adapters/openai.py +0 -0
  15. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_core/__init__.py +0 -0
  16. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_core/analytics.py +0 -0
  17. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_core/rtlx_s.py +0 -0
  18. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_core/streak.py +0 -0
  19. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_core/tlx_s.py +0 -0
  20. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/__init__.py +0 -0
  21. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/_generated/__init__.py +0 -0
  22. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/_migrations/alembic/env.py +0 -0
  23. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/_migrations/alembic/script.py.mako +0 -0
  24. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/_migrations/alembic/versions/0001_initial.py +0 -0
  25. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/_migrations/alembic/versions/0002_cognition_samples.py +0 -0
  26. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/_migrations/alembic/versions/0003_cognition_drivers.py +0 -0
  27. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/_migrations/alembic/versions/0004_transcript_intelligence.py +0 -0
  28. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/_migrations/alembic.ini +0 -0
  29. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/_runtime.py +0 -0
  30. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/adapters/__init__.py +0 -0
  31. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/adapters/anthropic.py +0 -0
  32. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/adapters/claude_code.py +0 -0
  33. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/adapters/crewai.py +0 -0
  34. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/adapters/langgraph.py +0 -0
  35. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/adapters/openai.py +0 -0
  36. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/auth.py +0 -0
  37. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/client.py +0 -0
  38. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/config.py +0 -0
  39. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/daemon.py +0 -0
  40. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/privacy.py +0 -0
  41. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/session.py +0 -0
  42. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/storage.py +0 -0
  43. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_sdk/types/__init__.py +0 -0
  44. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/__init__.py +0 -0
  45. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/analytics.py +0 -0
  46. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/compression.py +0 -0
  47. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/ingest.py +0 -0
  48. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/model.py +0 -0
  49. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/parsers/__init__.py +0 -0
  50. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/parsers/claude_code.py +0 -0
  51. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/parsers/codex.py +0 -0
  52. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/parsers/cursor.py +0 -0
  53. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/prices.py +0 -0
  54. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/schema.py +0 -0
  55. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/signals.py +0 -0
  56. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/_vendor_build/zeno_session_intel/taxonomy.py +0 -0
  57. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/data/outreach_suppression.txt +0 -0
  58. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/hatch_build.py +0 -0
  59. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/scripts/install-smoke.sh +0 -0
  60. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/__init__.py +0 -0
  61. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/doctor.py +0 -0
  62. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/hook_install.py +0 -0
  63. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/hud/__init__.py +0 -0
  64. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/hud/hud_install.py +0 -0
  65. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/hud/zeno_attention.py +0 -0
  66. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/hud/zeno_cognition.py +0 -0
  67. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/hud/zeno_hud.py +0 -0
  68. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/interview_invites.py +0 -0
  69. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/main.py +0 -0
  70. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/onboard.py +0 -0
  71. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/outreach.py +0 -0
  72. {zeno_cli-0.3.6 → zeno_cli-0.3.8}/src/zeno_cli/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zeno-cli
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: Zeno CLI - measure the supervision cost of AI-assisted work
5
5
  Project-URL: Homepage, https://zeno.center
6
6
  Project-URL: Repository, https://github.com/Marwan01/zeno
@@ -574,6 +574,20 @@ class ZenoApiClient:
574
574
  headers=headers,
575
575
  )
576
576
 
577
+ def capture_recent(self, *, limit: str | None = None) -> dict[str, Any]:
578
+ """GET /v1/capture/recent"""
579
+ query: dict[str, str] | None = None
580
+ headers: dict[str, str] | None = None
581
+ query = {}
582
+ if limit is not None:
583
+ query["limit"] = str(limit)
584
+ return self._request(
585
+ "GET",
586
+ "/v1/capture/recent",
587
+ query=query,
588
+ headers=headers,
589
+ )
590
+
577
591
  def capture_stream(self) -> dict[str, Any]:
578
592
  """GET /v1/capture/stream"""
579
593
  query: dict[str, str] | None = None
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "zeno-cli"
3
- version = "0.3.6"
3
+ version = "0.3.8"
4
4
  description = "Zeno CLI - measure the supervision cost of AI-assisted work"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -197,8 +197,8 @@ def _api_base_url() -> str:
197
197
  def _api_token() -> str | None:
198
198
  """Bearer token for the capture push, when one is available.
199
199
 
200
- Checks the explicit env var first (matches the SDK client's ZENO_API_TOKEN
201
- precedence), then the keyring slot `zeno login` writes (service "zeno-sdk",
200
+ Checks the explicit env var first (same precedence as the CLI's
201
+ resolve_api_token), then the keyring slot `zeno login` writes (service "zeno-sdk",
202
202
  key "clerk_jwt"). The keyring lookup is best-effort: keyring is an optional
203
203
  dependency and may be absent under the system python the hook runs on.
204
204
  Returns None on the tailnet Mini, where identity comes from a header the
@@ -279,9 +279,22 @@ def _idempotency_key(body: dict) -> str:
279
279
  return f"{body['device_id']}:{body['session_id']}:{entity}"
280
280
 
281
281
 
282
- def _post_ingest(body: dict, timeout_s: float) -> bool:
282
+ # Sentinel for "token not supplied -> resolve it here", distinct from a resolved
283
+ # None ("no token, tailnet path"). Lets _maybe_push pass the once-resolved value
284
+ # (str OR None) down so neither the public nor the Mini path re-reads the keyring
285
+ # per drained line.
286
+ _UNSET = object()
287
+
288
+
289
+ def _post_ingest(body: dict, timeout_s: float, token=_UNSET) -> bool:
283
290
  """POST one ingest body. Returns True on a 2xx, False on any failure. Never
284
- raises (all exceptions -> False). urllib + an explicit socket timeout only."""
291
+ raises (all exceptions -> False). urllib + an explicit socket timeout only.
292
+
293
+ ``token`` is the pre-resolved bearer (see _maybe_push); pass it so a drain of
294
+ many spooled lines does not re-read the OS keyring per line. When omitted
295
+ (_UNSET; direct callers/tests) it is resolved once here. An explicit None means
296
+ "no token" (tailnet path) and is used as-is without re-resolving.
297
+ """
285
298
  url = _api_base_url() + SYNC_PATH
286
299
  data = json.dumps(body).encode("utf-8")
287
300
  headers = {
@@ -291,9 +304,9 @@ def _post_ingest(body: dict, timeout_s: float) -> bool:
291
304
  # Attach the Clerk bearer token when one is available so the push authenticates
292
305
  # against a public API (Cloud Run). Absent it (tailnet Mini), the push relies on
293
306
  # the identity header the `tailscale serve` proxy injects - unchanged behavior.
294
- token = _api_token()
295
- if token:
296
- headers["Authorization"] = "Bearer " + token
307
+ resolved = _api_token() if token is _UNSET else token
308
+ if resolved:
309
+ headers["Authorization"] = "Bearer " + resolved
297
310
  req = urllib.request.Request(
298
311
  url,
299
312
  data=data,
@@ -342,10 +355,11 @@ def _trim_outbox(path: Path) -> None:
342
355
  _debug(f"trim_outbox failed: {exc}")
343
356
 
344
357
 
345
- def _drain_outbox(timeout_s: float) -> None:
358
+ def _drain_outbox(timeout_s: float, token=_UNSET) -> None:
346
359
  """After a live push, best-effort + time-boxed drain of a few spooled lines.
347
360
  Pushes oldest-first; stops at the budget, the line cap, or the first failure
348
- (so an offline window doesn't re-spin). Rewrites the outbox with the remainder."""
361
+ (so an offline window doesn't re-spin). Rewrites the outbox with the remainder.
362
+ ``token`` is threaded to _post_ingest so the keyring is read once per drain."""
349
363
  path = _outbox_path()
350
364
  try:
351
365
  if not path.exists():
@@ -367,7 +381,7 @@ def _drain_outbox(timeout_s: float) -> None:
367
381
  except Exception:
368
382
  consumed += 1 # unparseable - drop it, keep draining
369
383
  continue
370
- if not _post_ingest(body, timeout_s):
384
+ if not _post_ingest(body, timeout_s, token):
371
385
  break # stop on first failure; leave this line + the rest spooled
372
386
  consumed += 1
373
387
  if consumed == 0:
@@ -403,8 +417,12 @@ def _maybe_push(event: str, payload: dict) -> None:
403
417
  if body is None:
404
418
  return
405
419
  timeout_s = _sync_timeout_s()
406
- if _post_ingest(body, timeout_s):
407
- _drain_outbox(timeout_s)
420
+ # Resolve the bearer ONCE per hook invocation (an OS keyring read is not
421
+ # bounded by the socket timeout); thread it through the drain so a
422
+ # multi-line drain never re-reads the keyring on the hot Stop path.
423
+ token = _api_token()
424
+ if _post_ingest(body, timeout_s, token):
425
+ _drain_outbox(timeout_s, token)
408
426
  else:
409
427
  _spool(body)
410
428
  except Exception as exc: # network/build path must never break the hook
@@ -18,17 +18,17 @@ apps/api/src/zeno_api/auth.py).
18
18
 
19
19
  The security control on the callback today is the CSRF `state` token (browser +
20
20
  loopback, state-CSRF) - NOT PKCE. The bridge returns the token directly in the
21
- redirect; there is no authorization-code-for-token exchange, so a PKCE verifier
22
- would be unused theater. TODO: when the dashboard `/cli/authorize` bridge ships,
23
- implement a real OAuth code-for-token exchange (PKCE S256: send a code_challenge,
24
- receive a short-lived auth code on the callback, then POST code + code_verifier
25
- to the bridge to redeem the JWT) instead of receiving the token in the URL.
26
-
27
- OUT OF SCOPE / FOLLOW-UP: the dashboard `/cli/authorize` bridge page and the
28
- Clerk `zeno-api` JWT template do NOT exist yet. They are the only missing
29
- pieces for an end-to-end login. This module is fully unit-testable without them
30
- because `authorize_url` is injectable (env `ZENO_LOGIN_AUTHORIZE_URL`, flag
31
- `--authorize-url`) and the loopback leg is driven by a fake "browser" in tests.
21
+ redirect to the localhost loopback; there is no authorization-code-for-token
22
+ exchange, so a PKCE verifier would be unused theater. FOLLOW-UP (hardening): a
23
+ real OAuth code-for-token exchange (PKCE S256: send a code_challenge, receive a
24
+ short-lived auth code on the callback, then POST code + code_verifier to the
25
+ bridge to redeem the JWT) instead of receiving the token in the URL.
26
+
27
+ LIVE: the dashboard `/cli/authorize` bridge page (apps/cognition-dashboard) and
28
+ the Clerk `zeno-api` JWT template both exist; `DEFAULT_AUTHORIZE_URL` points at
29
+ the deployed bridge. The module stays fully unit-testable because `authorize_url`
30
+ is injectable (env `ZENO_LOGIN_AUTHORIZE_URL`, flag `--authorize-url`) and the
31
+ loopback leg is driven by a fake "browser" in tests.
32
32
  """
33
33
 
34
34
  from __future__ import annotations
File without changes
File without changes
File without changes
File without changes