cc-transcript 14.6.0__tar.gz → 14.6.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 (154) hide show
  1. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/PKG-INFO +1 -1
  2. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/__init__.py +7 -0
  3. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/__init__.pyi +13 -0
  4. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/_native.pyi +15 -5
  5. cc_transcript-14.6.1/cc_transcript/codex.py +212 -0
  6. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/discovery.py +7 -3
  7. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/judge/verdicts.py +6 -1
  8. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/pyproject.toml +1 -1
  9. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/codex/discovery.rs +30 -12
  10. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/codex/probe.rs +21 -0
  11. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/gateway.rs +9 -0
  12. cc_transcript-14.6.1/rust/crates/py/src/codex.rs +144 -0
  13. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/lib.rs +1 -0
  14. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/python.rs +20 -6
  15. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/transcript.rs +11 -5
  16. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/Cargo.lock +0 -0
  17. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/Cargo.toml +0 -0
  18. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/LICENSE +0 -0
  19. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/README.md +0 -0
  20. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/activity.py +0 -0
  21. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/activity_probe.py +0 -0
  22. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/builders.py +0 -0
  23. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/command.py +0 -0
  24. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/context.py +0 -0
  25. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/corrections.py +0 -0
  26. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/cost.py +0 -0
  27. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/decisions.py +0 -0
  28. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/disktruth.py +0 -0
  29. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/evidence.py +0 -0
  30. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/extract/__init__.py +0 -0
  31. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/extract/correct.py +0 -0
  32. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/facts.py +0 -0
  33. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/filterspec.py +0 -0
  34. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/heartbeats.py +0 -0
  35. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/ids.py +0 -0
  36. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/judge/__init__.py +0 -0
  37. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/judge/llm.py +0 -0
  38. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/judge/similar.py +0 -0
  39. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/ledger.py +0 -0
  40. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/literals.py +0 -0
  41. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/mining/__init__.py +0 -0
  42. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/mining/candidates.py +0 -0
  43. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/mining/confidence.py +0 -0
  44. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/mining/engine.py +0 -0
  45. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/mining/formats.py +0 -0
  46. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/mining/sampling.py +0 -0
  47. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/mining/signals.py +0 -0
  48. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/mining/sourcekind.py +0 -0
  49. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/mining/spec.py +0 -0
  50. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/mining/store.py +0 -0
  51. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/models.py +0 -0
  52. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/nlp.py +0 -0
  53. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/notifications.py +0 -0
  54. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/parser.py +0 -0
  55. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/py.typed +0 -0
  56. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/query.py +0 -0
  57. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/render.py +0 -0
  58. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/sentiment/__init__.py +0 -0
  59. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/sentiment/buckets.py +0 -0
  60. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/sentiment/data/afinn-en-165.tsv +0 -0
  61. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/sentiment/data/domain_overrides.tsv +0 -0
  62. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/sentiment/data/en-ewt.udpipe +0 -0
  63. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/sentiment/engine.py +0 -0
  64. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/sentiment/lexicon.py +0 -0
  65. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/sentiment/scorespec.py +0 -0
  66. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/synthetic.py +0 -0
  67. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/tools.py +0 -0
  68. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/cc_transcript/watch.py +0 -0
  69. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/Cargo.toml +0 -0
  70. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/commands/corrections.rs +0 -0
  71. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/commands/digest.rs +0 -0
  72. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/commands/facts.rs +0 -0
  73. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/commands/grep.rs +0 -0
  74. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/commands/list.rs +0 -0
  75. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/commands/mod.rs +0 -0
  76. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/commands/scratchpad.rs +0 -0
  77. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/commands/show.rs +0 -0
  78. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/commands/slice.rs +0 -0
  79. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/commands/stats.rs +0 -0
  80. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/commands/watch.rs +0 -0
  81. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/lib.rs +0 -0
  82. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/main.rs +0 -0
  83. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/output.rs +0 -0
  84. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/cli/src/target.rs +0 -0
  85. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/Cargo.toml +0 -0
  86. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/benches/parse.rs +0 -0
  87. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/benches/render.rs +0 -0
  88. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/benches/toolcall.rs +0 -0
  89. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/activity.rs +0 -0
  90. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/actor.rs +0 -0
  91. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/buckets.rs +0 -0
  92. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/codex/lower.rs +0 -0
  93. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/codex/mod.rs +0 -0
  94. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/codex/parse.rs +0 -0
  95. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/codex/protocol.rs +0 -0
  96. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/codex/types.rs +0 -0
  97. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/command.rs +0 -0
  98. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/context.rs +0 -0
  99. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/corrections.rs +0 -0
  100. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/cost.rs +0 -0
  101. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/discovery.rs +0 -0
  102. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/facts.rs +0 -0
  103. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/feedback.rs +0 -0
  104. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/filter.rs +0 -0
  105. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/ids.rs +0 -0
  106. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/lib.rs +0 -0
  107. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/literals/command.rs +0 -0
  108. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/literals/corrections.rs +0 -0
  109. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/literals/feedback.rs +0 -0
  110. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/literals/mining.rs +0 -0
  111. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/literals/mod.rs +0 -0
  112. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/literals/protocol.rs +0 -0
  113. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/notifications.rs +0 -0
  114. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/parse.rs +0 -0
  115. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/protocol.rs +0 -0
  116. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/pystr.rs +0 -0
  117. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/query.rs +0 -0
  118. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/render.rs +0 -0
  119. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/sqlite.rs +0 -0
  120. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/toolcall.rs +0 -0
  121. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/types.rs +0 -0
  122. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/value.rs +0 -0
  123. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/core/src/watch.rs +0 -0
  124. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/Cargo.toml +0 -0
  125. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/build.rs +0 -0
  126. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/data/command_prefix_pins.tsv +0 -0
  127. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/actor_bridge.rs +0 -0
  128. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/bin/stub_gen.rs +0 -0
  129. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/command.rs +0 -0
  130. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/context.rs +0 -0
  131. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/corrections.rs +0 -0
  132. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/discovery.rs +0 -0
  133. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/feedback.rs +0 -0
  134. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/lexicon.rs +0 -0
  135. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/mining.rs +0 -0
  136. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/nlp.rs +0 -0
  137. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/render.rs +0 -0
  138. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/score.rs +0 -0
  139. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/sqlite.rs +0 -0
  140. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/toolcall.rs +0 -0
  141. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/attachment.rs +0 -0
  142. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/blocks.rs +0 -0
  143. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/command.rs +0 -0
  144. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/convert.rs +0 -0
  145. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/dunder.rs +0 -0
  146. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/events.rs +0 -0
  147. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/meta.rs +0 -0
  148. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/mod.rs +0 -0
  149. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/print.rs +0 -0
  150. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/store.rs +0 -0
  151. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/system.rs +0 -0
  152. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/toolcall.rs +0 -0
  153. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/views/toolresult.rs +0 -0
  154. {cc_transcript-14.6.0 → cc_transcript-14.6.1}/rust/crates/py/src/watch.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-transcript
3
- Version: 14.6.0
3
+ Version: 14.6.1
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -181,6 +181,13 @@ EXPORTS: dict[str, str] = {
181
181
  "resolve",
182
182
  "subagent_paths",
183
183
  ),
184
+ "cc_transcript.codex": (
185
+ "CodexPendingItem",
186
+ "CodexRollout",
187
+ "CodexSessionInfo",
188
+ "CodexUsage",
189
+ "Lifecycle",
190
+ ),
184
191
  "cc_transcript.activity": (
185
192
  "Edit",
186
193
  "SessionActivity",
@@ -27,6 +27,14 @@ from cc_transcript.builders import (
27
27
  keep_only as keep_only,
28
28
  )
29
29
 
30
+ from cc_transcript.codex import (
31
+ CodexPendingItem as CodexPendingItem,
32
+ CodexRollout as CodexRollout,
33
+ CodexSessionInfo as CodexSessionInfo,
34
+ CodexUsage as CodexUsage,
35
+ Lifecycle as Lifecycle,
36
+ )
37
+
30
38
  from cc_transcript.command import (
31
39
  Command as Command,
32
40
  CommandLine as CommandLine,
@@ -302,6 +310,10 @@ __all__ = [
302
310
  "CacheCreation",
303
311
  "CandidatePair",
304
312
  "CcVersion",
313
+ "CodexPendingItem",
314
+ "CodexRollout",
315
+ "CodexSessionInfo",
316
+ "CodexUsage",
305
317
  "Command",
306
318
  "CommandLine",
307
319
  "CommandLineQuery",
@@ -342,6 +354,7 @@ __all__ = [
342
354
  "HydratedWindow",
343
355
  "InitInfo",
344
356
  "JUNK_USER_MESSAGE_RE",
357
+ "Lifecycle",
345
358
  "McpServer",
346
359
  "ModeEvent",
347
360
  "ModelPricing",
@@ -104,6 +104,9 @@ __all__ = [
104
104
  "activity_lift_from_events",
105
105
  "bucket_events_from_events",
106
106
  "cli_main",
107
+ "codex_discover",
108
+ "codex_resolve",
109
+ "codex_session_info",
107
110
  "command_parse",
108
111
  "command_prefixes",
109
112
  "context_capture_window",
@@ -1941,15 +1944,14 @@ class Transcript:
1941
1944
  The parsed events of a single transcript file, backed by the native parse
1942
1945
  output; ``events`` materializes lazy views on access.
1943
1946
 
1944
- One parse owns one ``Arc<Vec<Entry>>`` that every event and nested view shares,
1945
- so retaining any single view keeps that whole parse's entries alive; keeping one
1946
- event from each of several parses retains all of their entries. The live
1947
- :class:`~watch.WatchEvent` stream is exempt — its tailer builds a
1948
- one-entry Arc per yielded event, so a held watch event pins only itself.
1947
+ One parse owns one ``Arc<Vec<Entry>>`` shared by every view, so retaining any
1948
+ view keeps the whole parse's entries alive; the live
1949
+ :class:`~watch.WatchEvent` stream is exempt (one-entry Arc each).
1949
1950
 
1950
1951
  Attributes:
1951
1952
  path: The transcript's path on disk; None for a bytes parse.
1952
1953
  mtime: The transcript's modification time when parsed.
1954
+ provider: The transcript's source provider: "claude" or "codex".
1953
1955
  events: The parsed events, in file order.
1954
1956
  """
1955
1957
  @property
@@ -1957,6 +1959,8 @@ class Transcript:
1957
1959
  @property
1958
1960
  def mtime(self) -> builtins.float: ...
1959
1961
  @property
1962
+ def provider(self) -> typing.Literal["claude", "codex"]: ...
1963
+ @property
1960
1964
  def events(self) -> EventList: ...
1961
1965
 
1962
1966
  @typing.final
@@ -2193,6 +2197,12 @@ def cli_main() -> builtins.int:
2193
2197
  run, and the exit code returned verbatim for the wrapper's `sys.exit`.
2194
2198
  """
2195
2199
 
2200
+ def codex_discover(root: typing.Optional[builtins.str | os.PathLike | pathlib.Path] = None) -> builtins.list[tuple[pathlib.Path, builtins.str, builtins.bool]]: ...
2201
+
2202
+ def codex_resolve(session_id: builtins.str, root: typing.Optional[builtins.str | os.PathLike | pathlib.Path] = None) -> typing.Optional[pathlib.Path]: ...
2203
+
2204
+ def codex_session_info(path: builtins.str) -> dict[str, typing.Any]: ...
2205
+
2196
2206
  def command_parse(command: builtins.str) -> dict[str, typing.Any]: ...
2197
2207
 
2198
2208
  def command_prefixes(commands: typing.Sequence[builtins.str]) -> builtins.list[builtins.list[builtins.str]]: ...
@@ -0,0 +1,212 @@
1
+ """The codex session surface: discovery, resolution, and per-rollout session info.
2
+
3
+ A sync facade over the native codex functions, mirroring
4
+ :mod:`cc_transcript.discovery` for the ``~/.codex/sessions`` tree. The sessions-root
5
+ default is a facade concern; the native side scans exactly the root it is handed.
6
+
7
+ There is no parse function here: :func:`cc_transcript.parse` is already
8
+ provider-transparent — it sniffs a codex rollout, lowers it into the native event
9
+ model, and yields the same :class:`~cc_transcript.models.Transcript` view a Claude
10
+ transcript produces, with ``provider == "codex"``. This module adds only the codex
11
+ identity, lifecycle, and token-usage surface that the lowered event stream drops.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import TYPE_CHECKING, Literal
19
+
20
+ if TYPE_CHECKING:
21
+ from cc_transcript.ids import SessionId
22
+
23
+ SESSIONS_ROOT = Path.home() / ".codex" / "sessions"
24
+
25
+ Lifecycle = Literal["uninitialized", "no_instrumentation", "open", "completed", "aborted"]
26
+ """A codex rollout's turn-lifecycle state.
27
+
28
+ ``uninitialized`` — no content at all; ``no_instrumentation`` — content but no
29
+ ``task_started``/``task_complete`` brackets; ``open`` — the latest turn is still
30
+ running; ``completed`` — the latest turn finished; ``aborted`` — the latest turn was
31
+ interrupted.
32
+ """
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class CodexRollout:
37
+ """A rollout found in the codex sessions tree.
38
+
39
+ Attributes:
40
+ path: The rollout's path.
41
+ session_id: The session id encoded in the rollout filename.
42
+ compressed: Whether the rollout is a ``.jsonl.zst`` file.
43
+ """
44
+
45
+ path: Path
46
+ session_id: str
47
+ compressed: bool
48
+
49
+
50
+ @dataclass(frozen=True, slots=True)
51
+ class CodexUsage:
52
+ """Session-level token totals lowered from a codex rollout.
53
+
54
+ The totals are the last observed cumulative ``total_token_usage`` fields — codex
55
+ ``token_count`` events carry running totals, not per-turn deltas — so this is the
56
+ session's final accounting, not a sum.
57
+
58
+ Attributes:
59
+ input_tokens: Total input tokens, or None when never reported.
60
+ cached_input_tokens: Cached input tokens, or None when never reported.
61
+ output_tokens: Output tokens, or None when never reported.
62
+ reasoning_output_tokens: Reasoning output tokens, or None when never reported.
63
+ total_tokens: Total tokens, or None when never reported.
64
+ model_context_window: The model's context window, or None when never reported.
65
+ token_count_events: The number of ``token_count`` events observed.
66
+ """
67
+
68
+ input_tokens: int | None
69
+ cached_input_tokens: int | None
70
+ output_tokens: int | None
71
+ reasoning_output_tokens: int | None
72
+ total_tokens: int | None
73
+ model_context_window: int | None
74
+ token_count_events: int
75
+
76
+
77
+ @dataclass(frozen=True, slots=True)
78
+ class CodexPendingItem:
79
+ """A dangling tool call in the rollout's open turn.
80
+
81
+ Attributes:
82
+ tool_use_id: The call's ``call_id``.
83
+ name: The tool's name, such as ``exec``.
84
+ kind: How the call contributes to the verdict; always ``mid_tool`` for codex.
85
+ """
86
+
87
+ tool_use_id: str | None
88
+ name: str
89
+ kind: str
90
+
91
+
92
+ @dataclass(frozen=True, slots=True)
93
+ class CodexSessionInfo:
94
+ """The identity, lifecycle, and usage of one codex rollout.
95
+
96
+ Attributes:
97
+ rollout_thread_id: This rollout's own thread id (the session-meta ``id``).
98
+ session_id: The logical session id; a forked rollout inherits its origin's.
99
+ parent_thread_id: The spawning thread's id for a subagent rollout, else None.
100
+ forked_from_id: The origin thread's id when this rollout is a fork, else None.
101
+ cwd: The working directory recorded in the session meta.
102
+ originator: What launched the session, such as ``codex_exec``.
103
+ cli_version: The codex CLI version that wrote the rollout.
104
+ model_provider: The model provider, such as ``openai``.
105
+ lifecycle: The latest turn's lifecycle state.
106
+ turn_id: The latest turn's id, or None before any turn brackets.
107
+ pending: The dangling tool calls in an open turn, in document order.
108
+ last_event_epoch: The latest envelope timestamp as epoch seconds, or None.
109
+ usage: The session token totals, or None when no ``token_count`` event occurred.
110
+ """
111
+
112
+ rollout_thread_id: str | None
113
+ session_id: str | None
114
+ parent_thread_id: str | None
115
+ forked_from_id: str | None
116
+ cwd: str | None
117
+ originator: str | None
118
+ cli_version: str | None
119
+ model_provider: str | None
120
+ lifecycle: Lifecycle
121
+ turn_id: str | None
122
+ pending: tuple[CodexPendingItem, ...]
123
+ last_event_epoch: int | None
124
+ usage: CodexUsage | None
125
+
126
+
127
+ def sessions_root(root: Path | None = None) -> Path:
128
+ """The codex sessions root, defaulting to ``~/.codex/sessions``.
129
+
130
+ Args:
131
+ root: An explicit root; when None, :data:`SESSIONS_ROOT`.
132
+
133
+ Returns:
134
+ The root that :func:`discover` and :func:`find_transcript` scan.
135
+ """
136
+ return root or SESSIONS_ROOT
137
+
138
+
139
+ def discover(root: Path | None = None) -> tuple[CodexRollout, ...]:
140
+ """Every codex rollout under ``root``, newest first.
141
+
142
+ Rollouts live as ``rollout-<timestamp>-<session_id>.jsonl`` (or ``.jsonl.zst``)
143
+ files under the date-sharded sessions tree. Each result preserves its path,
144
+ session id, and compression state.
145
+
146
+ Args:
147
+ root: The sessions root; when None, :func:`sessions_root`.
148
+
149
+ Returns:
150
+ The rollouts, newest first; ``()`` when the root does not exist.
151
+
152
+ Example:
153
+ >>> for rollout in discover():
154
+ ... print(rollout.session_id, rollout.path)
155
+ """
156
+ from cc_transcript import _native
157
+
158
+ return tuple(CodexRollout(*rollout) for rollout in _native.codex_discover(sessions_root(root)))
159
+
160
+
161
+ def find_transcript(session_id: SessionId, root: Path | None = None) -> Path | None:
162
+ """Locates ``session_id``'s rollout under the codex sessions tree.
163
+
164
+ Only uncompressed ``.jsonl`` rollouts resolve. Compressed rollouts appear in
165
+ ``discover()`` but do not resolve until zstd support lands. The newest wins among
166
+ same-id uncompressed rollouts.
167
+
168
+ Args:
169
+ session_id: The codex session id, as it appears in the rollout filename.
170
+ root: The sessions root; when None, :func:`sessions_root`.
171
+
172
+ Returns:
173
+ The rollout path, or None when no rollout carries that session id.
174
+ """
175
+ from cc_transcript import _native
176
+
177
+ return _native.codex_resolve(session_id, sessions_root(root))
178
+
179
+
180
+ def session_info(path: Path) -> CodexSessionInfo:
181
+ """The identity, lifecycle, and token usage of the rollout at ``path``.
182
+
183
+ The native extension reads and folds the rollout in one pass, returning the
184
+ session identity, the turn lifecycle, any dangling calls, and the token totals.
185
+
186
+ Args:
187
+ path: The ``.jsonl`` rollout to inspect.
188
+
189
+ Returns:
190
+ The rollout's session info.
191
+ """
192
+ from cc_transcript import _native
193
+
194
+ payload = _native.codex_session_info(str(path))
195
+ identity = payload["identity"]
196
+ lifecycle = payload["lifecycle"]
197
+ usage = payload["usage"]
198
+ return CodexSessionInfo(
199
+ rollout_thread_id=identity["rollout_thread_id"],
200
+ session_id=identity["session_id"],
201
+ parent_thread_id=identity["parent_thread_id"],
202
+ forked_from_id=identity["forked_from_id"],
203
+ cwd=identity["cwd"],
204
+ originator=identity["originator"],
205
+ cli_version=identity["cli_version"],
206
+ model_provider=identity["model_provider"],
207
+ lifecycle=lifecycle["state"],
208
+ turn_id=lifecycle["turn_id"],
209
+ pending=tuple(CodexPendingItem(**item) for item in payload["pending"]),
210
+ last_event_epoch=payload["last_event_epoch"],
211
+ usage=CodexUsage(**usage) if usage is not None else None,
212
+ )
@@ -96,7 +96,8 @@ def resolve(session_id: SessionId, *, root: Path | None = None) -> Path | None:
96
96
  A successful lookup is memoized in ``TRANSCRIPT_MEMO`` under
97
97
  ``(session_id, resolved root)`` and revalidated on the next hit, so a
98
98
  repeated probe skips the recursive scan while a pruned transcript still
99
- forces a fresh one.
99
+ forces a fresh one. When the projects tree yields nothing, the codex
100
+ sessions tree is tried as a fallback, so a codex session id resolves too.
100
101
 
101
102
  Returns:
102
103
  The newest-mtime real path, or None when no transcript exists.
@@ -105,8 +106,11 @@ def resolve(session_id: SessionId, *, root: Path | None = None) -> Path | None:
105
106
  key = (session_id, base.resolve())
106
107
  if (cached := TRANSCRIPT_MEMO.get(key)) is not None and cached.exists():
107
108
  return cached
108
- if (hit := _native.discovery_find_transcript(base, session_id)) is None:
109
- return None
109
+ hit = _native.discovery_find_transcript(base, session_id)
110
+ if hit is None:
111
+ from cc_transcript import codex
112
+
113
+ return codex.find_transcript(session_id)
110
114
  TRANSCRIPT_MEMO[key] = hit
111
115
  return hit
112
116
 
@@ -334,6 +334,7 @@ async def run_verdicts[V](
334
334
  """
335
335
  counts = {"judged": 0, "failed": 0}
336
336
  limiter = asyncio.Semaphore(concurrency)
337
+ persister = asyncio.Lock()
337
338
 
338
339
  async def worker(row: Mapping[str, object]) -> None:
339
340
  async with limiter:
@@ -342,7 +343,11 @@ async def run_verdicts[V](
342
343
  except JudgeError:
343
344
  counts["failed"] += 1
344
345
  return
345
- await persist(row, verdict)
346
+ # The store's transaction is exclusive by contract; persists serialize on
347
+ # this lock so concurrent workers never race record_verdict into a
348
+ # TransactionConflictError. Judge concurrency is untouched.
349
+ async with persister:
350
+ await persist(row, verdict)
346
351
  counts["judged"] += 1
347
352
 
348
353
  async with asyncio.TaskGroup() as tg:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cc-transcript"
3
- version = "14.6.0" # informational — releases stamp the version from the git tag
3
+ version = "14.6.1" # informational — releases stamp the version from the git tag
4
4
  description = "Grep every Claude Code session you've ever run."
5
5
  readme = "README.md"
6
6
  license = "PolyForm-Noncommercial-1.0.0"
@@ -46,12 +46,7 @@ pub fn resolve(session_id: &str, root: &Path) -> Option<PathBuf> {
46
46
  found
47
47
  .iter()
48
48
  .find(|rollout| rollout.session_id == session_id && !rollout.compressed)
49
- .or_else(|| {
50
- found
51
- .iter()
52
- .find(|rollout| rollout.session_id == session_id)
53
- })
54
- .map(|rollout| rollout.path.clone())
49
+ .and_then(|rollout| fs::canonicalize(&rollout.path).ok())
55
50
  }
56
51
 
57
52
  fn walk(dir: &Path, visit: &mut impl FnMut(&Path)) {
@@ -173,20 +168,43 @@ mod tests {
173
168
  );
174
169
  assert_eq!(
175
170
  resolve("019b7000-0000-7000-8000-000000000002", &root),
176
- Some(twin.clone())
171
+ Some(fs::canonicalize(&twin).unwrap())
177
172
  );
178
173
  fs::remove_file(twin).unwrap();
179
- assert_eq!(
180
- resolve("019b7000-0000-7000-8000-000000000002", &root),
181
- Some(twin_zst)
182
- );
174
+ assert_eq!(resolve("019b7000-0000-7000-8000-000000000002", &root), None);
183
175
  assert_eq!(
184
176
  resolve("019b7000-0000-7000-8000-000000000001", &root),
185
- Some(oldest)
177
+ Some(fs::canonicalize(oldest).unwrap())
186
178
  );
187
179
  assert_eq!(resolve("019b7000-0000-7000-8000-000000000099", &root), None);
188
180
  assert_eq!(sessions_root(Some(&root)), root);
189
181
  assert!(sessions_root(None).ends_with(".codex/sessions"));
190
182
  fs::remove_dir_all(&root).ok();
191
183
  }
184
+
185
+ #[cfg(unix)]
186
+ #[test]
187
+ fn resolve_returns_canonical_path_through_symlinked_root() {
188
+ let base = std::env::temp_dir().join(format!(
189
+ "codex-discovery-symlink-{}-{:?}",
190
+ std::process::id(),
191
+ std::thread::current().id()
192
+ ));
193
+ fs::remove_dir_all(&base).ok();
194
+ fs::create_dir_all(&base).unwrap();
195
+ let root = base.join("sessions");
196
+ let rollout = touch(
197
+ &root,
198
+ "2026/01/02/rollout-2026-01-02T03-04-05-019b7000-0000-7000-8000-000000000003.jsonl",
199
+ );
200
+ let linked_root = base.join("linked-sessions");
201
+ std::os::unix::fs::symlink(&root, &linked_root).unwrap();
202
+
203
+ assert_eq!(
204
+ resolve("019b7000-0000-7000-8000-000000000003", &linked_root),
205
+ Some(fs::canonicalize(rollout).unwrap())
206
+ );
207
+
208
+ fs::remove_dir_all(&base).ok();
209
+ }
192
210
  }
@@ -21,6 +21,27 @@ pub enum Lifecycle {
21
21
  },
22
22
  }
23
23
 
24
+ impl Lifecycle {
25
+ pub fn as_str(&self) -> &'static str {
26
+ match self {
27
+ Lifecycle::Uninitialized => "uninitialized",
28
+ Lifecycle::NoInstrumentation => "no_instrumentation",
29
+ Lifecycle::Open { .. } => "open",
30
+ Lifecycle::Completed { .. } => "completed",
31
+ Lifecycle::Aborted { .. } => "aborted",
32
+ }
33
+ }
34
+
35
+ pub fn turn_id(&self) -> Option<&str> {
36
+ match self {
37
+ Lifecycle::Open { turn_id }
38
+ | Lifecycle::Completed { turn_id }
39
+ | Lifecycle::Aborted { turn_id, .. } => turn_id.as_deref(),
40
+ Lifecycle::Uninitialized | Lifecycle::NoInstrumentation => None,
41
+ }
42
+ }
43
+ }
44
+
24
45
  #[derive(Debug, Clone, PartialEq, Eq)]
25
46
  enum TurnState {
26
47
  Open,
@@ -23,6 +23,15 @@ pub enum Provider {
23
23
  Codex,
24
24
  }
25
25
 
26
+ impl Provider {
27
+ pub fn as_str(self) -> &'static str {
28
+ match self {
29
+ Provider::Claude => "claude",
30
+ Provider::Codex => "codex",
31
+ }
32
+ }
33
+ }
34
+
26
35
  #[derive(Debug)]
27
36
  pub struct ParsedTranscript {
28
37
  pub provider: Provider,
@@ -0,0 +1,144 @@
1
+ //! pyo3 exposure of the codex session surface: discovery, resolution, and the
2
+ //! per-rollout session-info fold the Python `cc_transcript.codex` facade
3
+ //! rehydrates. Paths cross as PathBuf, matching the CC discovery facade.
4
+
5
+ use std::path::PathBuf;
6
+
7
+ use pyo3::prelude::*;
8
+ use pyo3::types::{PyDict, PyList};
9
+
10
+ use cc_transcript_core::activity::PendingItem;
11
+ use cc_transcript_core::codex::{
12
+ self, lower, parse_codex_bytes, session_probe, CodexItem, CodexSession, CodexUsageAggregate,
13
+ Lifecycle,
14
+ };
15
+
16
+ struct CodexInfo {
17
+ rollout_thread_id: Option<String>,
18
+ session_id: Option<String>,
19
+ parent_thread_id: Option<String>,
20
+ forked_from_id: Option<String>,
21
+ cwd: Option<String>,
22
+ originator: Option<String>,
23
+ cli_version: Option<String>,
24
+ model_provider: Option<String>,
25
+ lifecycle: Lifecycle,
26
+ pending: Vec<PendingItem>,
27
+ last_event_epoch: Option<i64>,
28
+ usage: CodexUsageAggregate,
29
+ }
30
+
31
+ fn session_meta_provider(session: &CodexSession) -> Option<String> {
32
+ session
33
+ .entries
34
+ .iter()
35
+ .find_map(|entry| match &entry.item {
36
+ CodexItem::SessionMeta(m) if m.id.as_deref().is_some_and(|id| !id.is_empty()) => {
37
+ Some(m)
38
+ }
39
+ _ => None,
40
+ })
41
+ .and_then(|m| m.model_provider.clone())
42
+ }
43
+
44
+ fn read_info(path: &str) -> std::io::Result<CodexInfo> {
45
+ let session = parse_codex_bytes(&std::fs::read(path)?);
46
+ let probe = session_probe(&session);
47
+ let usage = lower(&session).usage;
48
+ let model_provider = session_meta_provider(&session);
49
+ Ok(CodexInfo {
50
+ rollout_thread_id: session.rollout_thread_id,
51
+ session_id: session.session_id,
52
+ parent_thread_id: session.parent_thread_id,
53
+ forked_from_id: session.forked_from_id,
54
+ cwd: session.cwd,
55
+ originator: session.originator,
56
+ cli_version: session.cli_version,
57
+ model_provider,
58
+ lifecycle: probe.lifecycle,
59
+ pending: probe.pending,
60
+ last_event_epoch: probe.last_event_epoch,
61
+ usage,
62
+ })
63
+ }
64
+
65
+ fn identity_dict<'py>(py: Python<'py>, info: &CodexInfo) -> PyResult<Bound<'py, PyDict>> {
66
+ let dict = PyDict::new(py);
67
+ dict.set_item("rollout_thread_id", info.rollout_thread_id.as_deref())?;
68
+ dict.set_item("session_id", info.session_id.as_deref())?;
69
+ dict.set_item("parent_thread_id", info.parent_thread_id.as_deref())?;
70
+ dict.set_item("forked_from_id", info.forked_from_id.as_deref())?;
71
+ dict.set_item("cwd", info.cwd.as_deref())?;
72
+ dict.set_item("originator", info.originator.as_deref())?;
73
+ dict.set_item("cli_version", info.cli_version.as_deref())?;
74
+ dict.set_item("model_provider", info.model_provider.as_deref())?;
75
+ Ok(dict)
76
+ }
77
+
78
+ fn pending_list<'py>(py: Python<'py>, pending: &[PendingItem]) -> PyResult<Bound<'py, PyList>> {
79
+ PyList::new(
80
+ py,
81
+ pending
82
+ .iter()
83
+ .map(|item| {
84
+ let entry = PyDict::new(py);
85
+ entry.set_item("tool_use_id", item.tool_use_id.as_deref())?;
86
+ entry.set_item("name", &item.name)?;
87
+ entry.set_item("kind", item.kind.as_str())?;
88
+ Ok(entry)
89
+ })
90
+ .collect::<PyResult<Vec<_>>>()?,
91
+ )
92
+ }
93
+
94
+ fn usage_dict<'py>(
95
+ py: Python<'py>,
96
+ usage: &CodexUsageAggregate,
97
+ ) -> PyResult<Option<Bound<'py, PyDict>>> {
98
+ if usage.token_count_events == 0 {
99
+ return Ok(None);
100
+ }
101
+ let dict = PyDict::new(py);
102
+ dict.set_item("input_tokens", usage.input_tokens)?;
103
+ dict.set_item("cached_input_tokens", usage.cached_input_tokens)?;
104
+ dict.set_item("output_tokens", usage.output_tokens)?;
105
+ dict.set_item("reasoning_output_tokens", usage.reasoning_output_tokens)?;
106
+ dict.set_item("total_tokens", usage.total_tokens)?;
107
+ dict.set_item("model_context_window", usage.model_context_window)?;
108
+ dict.set_item("token_count_events", usage.token_count_events)?;
109
+ Ok(Some(dict))
110
+ }
111
+
112
+ #[pyo3_stub_gen::derive::gen_stub_pyfunction]
113
+ #[pyfunction]
114
+ #[gen_stub(override_return_type(type_repr = "dict[str, typing.Any]", imports = ("typing",)))]
115
+ pub fn codex_session_info<'py>(py: Python<'py>, path: String) -> PyResult<Bound<'py, PyDict>> {
116
+ let info = py.detach(|| read_info(&path))?;
117
+ let out = PyDict::new(py);
118
+ out.set_item("identity", identity_dict(py, &info)?)?;
119
+ let lifecycle = PyDict::new(py);
120
+ lifecycle.set_item("state", info.lifecycle.as_str())?;
121
+ lifecycle.set_item("turn_id", info.lifecycle.turn_id())?;
122
+ out.set_item("lifecycle", lifecycle)?;
123
+ out.set_item("pending", pending_list(py, &info.pending)?)?;
124
+ out.set_item("last_event_epoch", info.last_event_epoch)?;
125
+ out.set_item("usage", usage_dict(py, &info.usage)?)?;
126
+ Ok(out)
127
+ }
128
+
129
+ #[pyo3_stub_gen::derive::gen_stub_pyfunction]
130
+ #[pyfunction]
131
+ #[pyo3(signature = (root=None))]
132
+ pub fn codex_discover(root: Option<PathBuf>) -> Vec<(PathBuf, String, bool)> {
133
+ codex::discover(&codex::sessions_root(root.as_deref()))
134
+ .into_iter()
135
+ .map(|rollout| (rollout.path, rollout.session_id, rollout.compressed))
136
+ .collect()
137
+ }
138
+
139
+ #[pyo3_stub_gen::derive::gen_stub_pyfunction]
140
+ #[pyfunction]
141
+ #[pyo3(signature = (session_id, root=None))]
142
+ pub fn codex_resolve(session_id: &str, root: Option<PathBuf>) -> Option<PathBuf> {
143
+ codex::resolve(session_id, &codex::sessions_root(root.as_deref()))
144
+ }
@@ -4,6 +4,7 @@ pub fn stub_info() -> pyo3_stub_gen::Result<pyo3_stub_gen::StubInfo> {
4
4
  }
5
5
 
6
6
  mod actor_bridge;
7
+ mod codex;
7
8
  mod command;
8
9
  mod context;
9
10
  mod corrections;