cc-blackbox 0.2.0__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 (31) hide show
  1. cc_blackbox-0.2.0/.editorconfig +20 -0
  2. cc_blackbox-0.2.0/.github/workflows/ci.yml +51 -0
  3. cc_blackbox-0.2.0/.github/workflows/publish.yml +44 -0
  4. cc_blackbox-0.2.0/.gitignore +29 -0
  5. cc_blackbox-0.2.0/LICENSE +21 -0
  6. cc_blackbox-0.2.0/PKG-INFO +171 -0
  7. cc_blackbox-0.2.0/README.md +148 -0
  8. cc_blackbox-0.2.0/pyproject.toml +71 -0
  9. cc_blackbox-0.2.0/src/cc_blackbox/__init__.py +101 -0
  10. cc_blackbox-0.2.0/src/cc_blackbox/compatibility.py +112 -0
  11. cc_blackbox-0.2.0/src/cc_blackbox/errors.py +66 -0
  12. cc_blackbox-0.2.0/src/cc_blackbox/messaging/__init__.py +21 -0
  13. cc_blackbox-0.2.0/src/cc_blackbox/messaging/client.py +224 -0
  14. cc_blackbox-0.2.0/src/cc_blackbox/messaging/protocol.py +164 -0
  15. cc_blackbox-0.2.0/src/cc_blackbox/messaging/transport.py +75 -0
  16. cc_blackbox-0.2.0/src/cc_blackbox/paths.py +125 -0
  17. cc_blackbox-0.2.0/src/cc_blackbox/profiles.py +284 -0
  18. cc_blackbox-0.2.0/src/cc_blackbox/py.typed +0 -0
  19. cc_blackbox-0.2.0/src/cc_blackbox/sessions/__init__.py +19 -0
  20. cc_blackbox-0.2.0/src/cc_blackbox/sessions/active.py +352 -0
  21. cc_blackbox-0.2.0/src/cc_blackbox/sessions/liveness.py +63 -0
  22. cc_blackbox-0.2.0/src/cc_blackbox/sessions/registry.py +54 -0
  23. cc_blackbox-0.2.0/tests/conftest.py +44 -0
  24. cc_blackbox-0.2.0/tests/records.py +80 -0
  25. cc_blackbox-0.2.0/tests/test_compatibility.py +119 -0
  26. cc_blackbox-0.2.0/tests/test_live_contract.py +185 -0
  27. cc_blackbox-0.2.0/tests/test_messaging.py +341 -0
  28. cc_blackbox-0.2.0/tests/test_paths.py +92 -0
  29. cc_blackbox-0.2.0/tests/test_profiles.py +253 -0
  30. cc_blackbox-0.2.0/tests/test_sessions.py +297 -0
  31. cc_blackbox-0.2.0/uv.lock +451 -0
@@ -0,0 +1,20 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+
10
+ [*.py]
11
+ indent_size = 4
12
+
13
+ [*.{toml,yml,yaml,json}]
14
+ indent_size = 2
15
+
16
+ [*.md]
17
+ trim_trailing_whitespace = false
18
+
19
+ [Makefile]
20
+ indent_style = tab
@@ -0,0 +1,51 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ name: Lint and type check
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - uses: astral-sh/setup-uv@v7
16
+ with:
17
+ enable-cache: true
18
+
19
+ - name: Install dependencies
20
+ run: uv sync --locked --dev
21
+
22
+ - name: Ruff check
23
+ run: uv run ruff check --output-format github .
24
+
25
+ - name: Ruff format check
26
+ run: uv run ruff format --check .
27
+
28
+ - name: Mypy
29
+ run: uv run mypy
30
+
31
+ test:
32
+ name: Test (${{ matrix.os }}, Python ${{ matrix.python-version }})
33
+ runs-on: ${{ matrix.os }}
34
+ strategy:
35
+ fail-fast: false
36
+ matrix:
37
+ os: [ubuntu-latest, macos-latest]
38
+ python-version: ["3.11", "3.12", "3.13"]
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+
42
+ - uses: astral-sh/setup-uv@v7
43
+ with:
44
+ python-version: ${{ matrix.python-version }}
45
+ enable-cache: true
46
+
47
+ - name: Install dependencies
48
+ run: uv sync --locked --dev
49
+
50
+ - name: Run tests
51
+ run: uv run pytest
@@ -0,0 +1,44 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build:
10
+ name: Build
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ with:
15
+ fetch-depth: 0
16
+
17
+ - uses: astral-sh/setup-uv@v7
18
+ with:
19
+ enable-cache: true
20
+
21
+ - name: Build package
22
+ run: uv build
23
+
24
+ - uses: actions/upload-artifact@v4
25
+ with:
26
+ name: dist
27
+ path: dist/
28
+
29
+ publish:
30
+ name: Publish
31
+ needs: build
32
+ runs-on: ubuntu-latest
33
+ environment:
34
+ name: pypi
35
+ url: https://pypi.org/p/cc-blackbox
36
+ permissions:
37
+ id-token: write
38
+ steps:
39
+ - uses: actions/download-artifact@v4
40
+ with:
41
+ name: dist
42
+ path: dist/
43
+
44
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,29 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ build/
5
+ dist/
6
+ *.egg-info/
7
+
8
+ # Virtual environments
9
+ .venv/
10
+
11
+ # IDE
12
+ .idea/
13
+ .vscode/
14
+ *.swp
15
+ *~
16
+
17
+ # Tool caches
18
+ .pytest_cache/
19
+ .mypy_cache/
20
+ .ruff_cache/
21
+ .coverage
22
+ htmlcov/
23
+
24
+ # OS
25
+ .DS_Store
26
+ Thumbs.db
27
+
28
+ # Claude Code (local plans and research notes)
29
+ .claude/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Filip Procházka
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.5
2
+ Name: cc-blackbox
3
+ Version: 0.2.0
4
+ Summary: Unofficial, not affiliated with or endorsed by Anthropic: typed Python access to a running Claude Code (cc), its profiles, sessions and messaging, through undocumented internals that can change in any release
5
+ Project-URL: Homepage, https://github.com/fprochazka/cc-blackbox
6
+ Project-URL: Repository, https://github.com/fprochazka/cc-blackbox
7
+ Author-email: Filip Procházka <mr@fprochazka.cz>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: claude,claude-code,messaging,sdk,session
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: pyyaml>=6.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ > **Unofficial.** This project is not affiliated with or endorsed by Anthropic. It relies on undocumented Claude Code internals that can change in any Claude Code release.
25
+
26
+ # cc-blackbox
27
+
28
+ "cc" stands for Claude Code. cc-blackbox is a typed Python library for tools that work next to a running [Claude Code](https://code.claude.com): find the Claude Code profiles on the machine, list the sessions that run under them, and send a message into one of them.
29
+
30
+ A typical user is a background watcher started from a Claude Code session, for example one that polls CI and tells the session when a pipeline fails.
31
+
32
+ Claude Code does not document any of this. The library reads the files Claude Code writes and speaks the socket protocol Claude Code uses between its own sessions, as found in Claude Code 2.1.x. Read [Limitations](#limitations) before you depend on it.
33
+
34
+ ## Install
35
+
36
+ ```shell
37
+ uv add cc-blackbox
38
+ ```
39
+
40
+ or `pip install cc-blackbox`. It needs Python 3.11 or newer and depends only on PyYAML.
41
+
42
+ To use changes that are not released to PyPI yet, pin a tag of this repository as a git source:
43
+
44
+ ```toml
45
+ [project]
46
+ dependencies = ["cc-blackbox"]
47
+
48
+ [tool.uv.sources]
49
+ cc-blackbox = { git = "https://github.com/fprochazka/cc-blackbox", tag = "v0.2.0" }
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ List the profiles, list the sessions that run under them, pick one and send it a message:
55
+
56
+ ```python
57
+ from cc_blackbox import find_session, list_active_sessions, list_profiles, send_message
58
+
59
+ for profile in list_profiles():
60
+ print(profile.name, profile.source, profile.config_dir)
61
+
62
+ sessions = list_active_sessions()
63
+ for session in sessions:
64
+ print(session.address, session.status, session.cwd)
65
+
66
+ target = find_session("personal/my-project", sessions=sessions)
67
+ send_message(
68
+ target,
69
+ "Pipeline #1234 failed in job `test`. Logs: /tmp/ci-1234/",
70
+ sender="ci-watcher",
71
+ sender_name="CI watcher",
72
+ )
73
+ ```
74
+
75
+ A tool started from inside a session, directly or from one of its subagents, finds that session without any arguments:
76
+
77
+ ```python
78
+ from cc_blackbox import RateLimitedSender, current_session
79
+
80
+ session = current_session()
81
+ if session is not None:
82
+ print(session.scratchpad_dir)
83
+ notifier = RateLimitedSender("ci-watcher", sender_name="CI watcher")
84
+ notifier.send(session, "Pipeline #1234 passed.")
85
+ ```
86
+
87
+ ### Profiles
88
+
89
+ Claude Code reads its config dir from `CLAUDE_CONFIG_DIR`, or uses `~/.claude`. `list_profiles()` collects every config dir it can find:
90
+
91
+ - `~/.claude`, named `default`;
92
+ - the profiles of [claude-code-auth-switch](https://github.com/fprochazka/claude-code-auth-switch), from `~/.claude/auth-switch.yaml` or the legacy `~/.claude-profiles/config.yaml`;
93
+ - other config dirs under the auth-switch profiles dir, for example a profile removed from the config whose dir stayed behind;
94
+ - `$CLAUDE_CONFIG_DIR`.
95
+
96
+ Each profile has a name that is unique in the result and stable across runs. A dir whose basename clashes with another name gets `cfg-<8 hex chars>`.
97
+
98
+ ### Sessions
99
+
100
+ `list_active_sessions()` reads `<config dir>/sessions/<pid>.json`, which every running session writes, and drops records whose process has ended. Every field except the pid can be `None`, because the record shape changes between Claude Code versions. `session.raw` holds the whole record.
101
+
102
+ `find_session()` accepts an address (`<profile>/<name>`), a session name, a session id, `pid:<n>`, or a unique prefix of a session id. Session names are not unique: Claude Code generates names like `my-project-a1` without checking for collisions, so an ambiguous query raises `AmbiguousSession` with the candidates.
103
+
104
+ `scratchpad_dir` is the session's scratchpad directory. Claude Code creates it on first use, so it may not exist yet.
105
+
106
+ ### Messages
107
+
108
+ `send_message()` writes one frame to the session's messaging socket. The receiving model sees your text after the line `Another Claude session sent a message:` and before a fixed footer that says the message did not come from the user. It treats the message as a request from a teammate, not as an instruction from the user.
109
+
110
+ - `sender` is the receiver's rate-limit key. The model never sees it. Give each independent sending process its own label.
111
+ - With `sender_name`, the text is wrapped in a `<cross-session-message>` envelope, so the receiver's terminal shows it as a message from that name. The envelope's reply address is `did:<sender>`, which the model cannot send a reply to.
112
+ - `RateLimitedSender` stays below the receiver's rate limit and joins queued messages into one.
113
+ - `rename_session()` asks a session to rename itself.
114
+
115
+ ## Limitations
116
+
117
+ - **This is an undocumented, reverse-engineered interface.** Any Claude Code release can change the record format, the socket protocol or the feature flags behind them. The library parses records tolerantly and checks what each session declares, see [Compatibility](#compatibility). Pin a tag and upgrade on purpose.
118
+ - **Delivery is best effort.** The receiver never answers on the socket, so a successful send means only that the bytes were written. The receiver then drops a message without telling you when its sender exceeds 30 messages in a burst or one every 2 seconds, when the same text arrived in the last 30 seconds, or when 50 messages already wait in its queue. Put a sequence number or a time into each message so that two real events with the same text both arrive.
119
+ - **A session in bypass-permissions mode holds peer messages** until its user approves each one. A tool that messages such a session looks silent until then.
120
+ - **The receiver does not verify the sender.** Any process of the same user can connect to the socket and claim any name. Do not put another session's name or address into your messages.
121
+ - **Messages always reach the main session.** A subagent cannot be addressed.
122
+
123
+ ### Compatibility
124
+
125
+ cc-blackbox has its own version numbers and never compares Claude Code versions. Claude Code updates more often than the tools built on this library, so a session running a newer Claude Code is fine as long as what it declares is aligned: if the protocol is aligned, it just works.
126
+
127
+ `check_compatibility(session)`, also available as `session.compatibility`, checks each feature against the session's registry record:
128
+
129
+ - `messaging` needs a `peerProtocol` this version speaks (currently 1) and a `messagingSocketPath`.
130
+ - `scratchpad` needs the `sessionId` and `cwd` strings.
131
+ - `liveness` needs the `pid`, and on Linux the `procStart` string.
132
+
133
+ New record fields and new peer features are additions and do not count against a session. `send_message()` raises `UnsupportedProtocol`, with the reasons, only when `messaging` is not aligned. Nothing else warns.
134
+
135
+ `TESTED_CLAUDE_CODE_VERSIONS` lists the Claude Code versions that the [live contract test](#live-contract-test) passed against: 2.1.319. It is information only.
136
+
137
+ ### Platform support
138
+
139
+ - **Linux:** everything works. Liveness checks compare the process start time, which catches reused pids. The scratchpad path uses the temp dir from the session's settings files or its process environment.
140
+ - **macOS:** profiles, sessions and messaging use the same code as on Linux. Liveness checks the pid only. When the session's settings files do not set `CLAUDE_CODE_TMPDIR`, the scratchpad path is a guess based on this process's environment, and `scratchpad_dir_inferred` is `True`. This has not yet been checked against a real macOS install.
141
+ - **Windows:** profiles and sessions can be read, but liveness is not checked. Messaging raises `MessagingUnavailable`: Claude Code uses named pipes with a token there, which this version does not implement.
142
+
143
+ ## Development
144
+
145
+ ```shell
146
+ uv sync
147
+ uv run ruff check .
148
+ uv run ruff format --check .
149
+ uv run mypy
150
+ uv run pytest
151
+ ```
152
+
153
+ The tests build config dirs with anonymised session records in a temp dir, and check the messaging bytes against a Unix socket server started by the test. They never touch a real Claude Code.
154
+
155
+ ### Live contract test
156
+
157
+ The live contract test checks that the installed Claude Code still matches what cc-blackbox expects. Run it after a Claude Code update, and add the version to `TESTED_CLAUDE_CODE_VERSIONS` when it passes:
158
+
159
+ ```shell
160
+ uv run pytest -m live -s
161
+ ```
162
+
163
+ The test starts a headless session with `claude -p` in a temporary directory and a socket path of its own, using the profile in your `CLAUDE_CONFIG_DIR`. It waits for the session's registry record, checks `check_compatibility()`, sends a marker message, and checks that the marker is in the session's transcript and in its reply. Then it stops the session and deletes the transcript.
164
+
165
+ - It needs the `claude` CLI on `PATH`, logged in.
166
+ - It spends model usage: two short turns on `haiku`. Set `CC_BLACKBOX_LIVE_MODEL` to use another model.
167
+ - The default `pytest` run and CI skip it.
168
+
169
+ ## License
170
+
171
+ MIT
@@ -0,0 +1,148 @@
1
+ > **Unofficial.** This project is not affiliated with or endorsed by Anthropic. It relies on undocumented Claude Code internals that can change in any Claude Code release.
2
+
3
+ # cc-blackbox
4
+
5
+ "cc" stands for Claude Code. cc-blackbox is a typed Python library for tools that work next to a running [Claude Code](https://code.claude.com): find the Claude Code profiles on the machine, list the sessions that run under them, and send a message into one of them.
6
+
7
+ A typical user is a background watcher started from a Claude Code session, for example one that polls CI and tells the session when a pipeline fails.
8
+
9
+ Claude Code does not document any of this. The library reads the files Claude Code writes and speaks the socket protocol Claude Code uses between its own sessions, as found in Claude Code 2.1.x. Read [Limitations](#limitations) before you depend on it.
10
+
11
+ ## Install
12
+
13
+ ```shell
14
+ uv add cc-blackbox
15
+ ```
16
+
17
+ or `pip install cc-blackbox`. It needs Python 3.11 or newer and depends only on PyYAML.
18
+
19
+ To use changes that are not released to PyPI yet, pin a tag of this repository as a git source:
20
+
21
+ ```toml
22
+ [project]
23
+ dependencies = ["cc-blackbox"]
24
+
25
+ [tool.uv.sources]
26
+ cc-blackbox = { git = "https://github.com/fprochazka/cc-blackbox", tag = "v0.2.0" }
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ List the profiles, list the sessions that run under them, pick one and send it a message:
32
+
33
+ ```python
34
+ from cc_blackbox import find_session, list_active_sessions, list_profiles, send_message
35
+
36
+ for profile in list_profiles():
37
+ print(profile.name, profile.source, profile.config_dir)
38
+
39
+ sessions = list_active_sessions()
40
+ for session in sessions:
41
+ print(session.address, session.status, session.cwd)
42
+
43
+ target = find_session("personal/my-project", sessions=sessions)
44
+ send_message(
45
+ target,
46
+ "Pipeline #1234 failed in job `test`. Logs: /tmp/ci-1234/",
47
+ sender="ci-watcher",
48
+ sender_name="CI watcher",
49
+ )
50
+ ```
51
+
52
+ A tool started from inside a session, directly or from one of its subagents, finds that session without any arguments:
53
+
54
+ ```python
55
+ from cc_blackbox import RateLimitedSender, current_session
56
+
57
+ session = current_session()
58
+ if session is not None:
59
+ print(session.scratchpad_dir)
60
+ notifier = RateLimitedSender("ci-watcher", sender_name="CI watcher")
61
+ notifier.send(session, "Pipeline #1234 passed.")
62
+ ```
63
+
64
+ ### Profiles
65
+
66
+ Claude Code reads its config dir from `CLAUDE_CONFIG_DIR`, or uses `~/.claude`. `list_profiles()` collects every config dir it can find:
67
+
68
+ - `~/.claude`, named `default`;
69
+ - the profiles of [claude-code-auth-switch](https://github.com/fprochazka/claude-code-auth-switch), from `~/.claude/auth-switch.yaml` or the legacy `~/.claude-profiles/config.yaml`;
70
+ - other config dirs under the auth-switch profiles dir, for example a profile removed from the config whose dir stayed behind;
71
+ - `$CLAUDE_CONFIG_DIR`.
72
+
73
+ Each profile has a name that is unique in the result and stable across runs. A dir whose basename clashes with another name gets `cfg-<8 hex chars>`.
74
+
75
+ ### Sessions
76
+
77
+ `list_active_sessions()` reads `<config dir>/sessions/<pid>.json`, which every running session writes, and drops records whose process has ended. Every field except the pid can be `None`, because the record shape changes between Claude Code versions. `session.raw` holds the whole record.
78
+
79
+ `find_session()` accepts an address (`<profile>/<name>`), a session name, a session id, `pid:<n>`, or a unique prefix of a session id. Session names are not unique: Claude Code generates names like `my-project-a1` without checking for collisions, so an ambiguous query raises `AmbiguousSession` with the candidates.
80
+
81
+ `scratchpad_dir` is the session's scratchpad directory. Claude Code creates it on first use, so it may not exist yet.
82
+
83
+ ### Messages
84
+
85
+ `send_message()` writes one frame to the session's messaging socket. The receiving model sees your text after the line `Another Claude session sent a message:` and before a fixed footer that says the message did not come from the user. It treats the message as a request from a teammate, not as an instruction from the user.
86
+
87
+ - `sender` is the receiver's rate-limit key. The model never sees it. Give each independent sending process its own label.
88
+ - With `sender_name`, the text is wrapped in a `<cross-session-message>` envelope, so the receiver's terminal shows it as a message from that name. The envelope's reply address is `did:<sender>`, which the model cannot send a reply to.
89
+ - `RateLimitedSender` stays below the receiver's rate limit and joins queued messages into one.
90
+ - `rename_session()` asks a session to rename itself.
91
+
92
+ ## Limitations
93
+
94
+ - **This is an undocumented, reverse-engineered interface.** Any Claude Code release can change the record format, the socket protocol or the feature flags behind them. The library parses records tolerantly and checks what each session declares, see [Compatibility](#compatibility). Pin a tag and upgrade on purpose.
95
+ - **Delivery is best effort.** The receiver never answers on the socket, so a successful send means only that the bytes were written. The receiver then drops a message without telling you when its sender exceeds 30 messages in a burst or one every 2 seconds, when the same text arrived in the last 30 seconds, or when 50 messages already wait in its queue. Put a sequence number or a time into each message so that two real events with the same text both arrive.
96
+ - **A session in bypass-permissions mode holds peer messages** until its user approves each one. A tool that messages such a session looks silent until then.
97
+ - **The receiver does not verify the sender.** Any process of the same user can connect to the socket and claim any name. Do not put another session's name or address into your messages.
98
+ - **Messages always reach the main session.** A subagent cannot be addressed.
99
+
100
+ ### Compatibility
101
+
102
+ cc-blackbox has its own version numbers and never compares Claude Code versions. Claude Code updates more often than the tools built on this library, so a session running a newer Claude Code is fine as long as what it declares is aligned: if the protocol is aligned, it just works.
103
+
104
+ `check_compatibility(session)`, also available as `session.compatibility`, checks each feature against the session's registry record:
105
+
106
+ - `messaging` needs a `peerProtocol` this version speaks (currently 1) and a `messagingSocketPath`.
107
+ - `scratchpad` needs the `sessionId` and `cwd` strings.
108
+ - `liveness` needs the `pid`, and on Linux the `procStart` string.
109
+
110
+ New record fields and new peer features are additions and do not count against a session. `send_message()` raises `UnsupportedProtocol`, with the reasons, only when `messaging` is not aligned. Nothing else warns.
111
+
112
+ `TESTED_CLAUDE_CODE_VERSIONS` lists the Claude Code versions that the [live contract test](#live-contract-test) passed against: 2.1.319. It is information only.
113
+
114
+ ### Platform support
115
+
116
+ - **Linux:** everything works. Liveness checks compare the process start time, which catches reused pids. The scratchpad path uses the temp dir from the session's settings files or its process environment.
117
+ - **macOS:** profiles, sessions and messaging use the same code as on Linux. Liveness checks the pid only. When the session's settings files do not set `CLAUDE_CODE_TMPDIR`, the scratchpad path is a guess based on this process's environment, and `scratchpad_dir_inferred` is `True`. This has not yet been checked against a real macOS install.
118
+ - **Windows:** profiles and sessions can be read, but liveness is not checked. Messaging raises `MessagingUnavailable`: Claude Code uses named pipes with a token there, which this version does not implement.
119
+
120
+ ## Development
121
+
122
+ ```shell
123
+ uv sync
124
+ uv run ruff check .
125
+ uv run ruff format --check .
126
+ uv run mypy
127
+ uv run pytest
128
+ ```
129
+
130
+ The tests build config dirs with anonymised session records in a temp dir, and check the messaging bytes against a Unix socket server started by the test. They never touch a real Claude Code.
131
+
132
+ ### Live contract test
133
+
134
+ The live contract test checks that the installed Claude Code still matches what cc-blackbox expects. Run it after a Claude Code update, and add the version to `TESTED_CLAUDE_CODE_VERSIONS` when it passes:
135
+
136
+ ```shell
137
+ uv run pytest -m live -s
138
+ ```
139
+
140
+ The test starts a headless session with `claude -p` in a temporary directory and a socket path of its own, using the profile in your `CLAUDE_CONFIG_DIR`. It waits for the session's registry record, checks `check_compatibility()`, sends a marker message, and checks that the marker is in the session's transcript and in its reply. Then it stops the session and deletes the transcript.
141
+
142
+ - It needs the `claude` CLI on `PATH`, logged in.
143
+ - It spends model usage: two short turns on `haiku`. Set `CC_BLACKBOX_LIVE_MODEL` to use another model.
144
+ - The default `pytest` run and CI skip it.
145
+
146
+ ## License
147
+
148
+ MIT
@@ -0,0 +1,71 @@
1
+ [project]
2
+ name = "cc-blackbox"
3
+ version = "0.2.0"
4
+ description = "Unofficial, not affiliated with or endorsed by Anthropic: typed Python access to a running Claude Code (cc), its profiles, sessions and messaging, through undocumented internals that can change in any release"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "Filip Procházka", email = "mr@fprochazka.cz" }
10
+ ]
11
+ requires-python = ">=3.11"
12
+ keywords = ["claude-code", "claude", "session", "messaging", "sdk"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "Operating System :: POSIX :: Linux",
17
+ "Operating System :: MacOS",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: Software Development :: Libraries",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = [
25
+ "pyyaml>=6.0",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/fprochazka/cc-blackbox"
30
+ Repository = "https://github.com/fprochazka/cc-blackbox"
31
+
32
+ [build-system]
33
+ requires = ["hatchling"]
34
+ build-backend = "hatchling.build"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/cc_blackbox"]
38
+
39
+ [tool.uv]
40
+ package = true
41
+
42
+ [tool.ruff]
43
+ line-length = 120
44
+ target-version = "py311"
45
+
46
+ [tool.ruff.lint]
47
+ select = ["E", "F", "I", "UP", "B", "C4", "SIM"]
48
+ ignore = []
49
+
50
+ [tool.ruff.format]
51
+ quote-style = "double"
52
+
53
+ [tool.mypy]
54
+ strict = true
55
+ python_version = "3.11"
56
+ files = ["src", "tests"]
57
+
58
+ [tool.pytest.ini_options]
59
+ testpaths = ["tests"]
60
+ addopts = "-m 'not live'"
61
+ markers = [
62
+ "live: starts a real headless Claude Code session and spends model usage; run with `pytest -m live -s`",
63
+ ]
64
+
65
+ [dependency-groups]
66
+ dev = [
67
+ "mypy>=1.15",
68
+ "pytest>=8.0.0",
69
+ "ruff>=0.11.0",
70
+ "types-pyyaml>=6.0",
71
+ ]
@@ -0,0 +1,101 @@
1
+ """Typed access to a running Claude Code (cc): profiles, active sessions and cross-session messaging.
2
+
3
+ Unofficial and not affiliated with or endorsed by Anthropic. The package relies on undocumented Claude Code internals
4
+ that can change in any Claude Code release.
5
+
6
+ Everything importable from this package is the public API. Submodules are implementation details.
7
+ """
8
+
9
+ from cc_blackbox.compatibility import (
10
+ SUPPORTED_PEER_PROTOCOLS,
11
+ TESTED_CLAUDE_CODE_VERSIONS,
12
+ Compatibility,
13
+ CompatibilityFeature,
14
+ check_compatibility,
15
+ )
16
+ from cc_blackbox.errors import (
17
+ AmbiguousSession,
18
+ AuthSwitchConfigError,
19
+ ClaudeBlackboxError,
20
+ MessageTooLarge,
21
+ MessagingError,
22
+ MessagingUnavailable,
23
+ ProfileNotFound,
24
+ SessionGone,
25
+ SessionNotFound,
26
+ UnsupportedProtocol,
27
+ )
28
+ from cc_blackbox.messaging import (
29
+ MessagingTarget,
30
+ Priority,
31
+ RateLimitedSender,
32
+ SentMessage,
33
+ build_envelope,
34
+ percent_encode_address,
35
+ rename_session,
36
+ send_message,
37
+ )
38
+ from cc_blackbox.paths import claude_tmp_root, project_slug, scratchpad_dir
39
+ from cc_blackbox.profiles import (
40
+ AuthSwitchConfig,
41
+ AuthSwitchProfile,
42
+ ClaudeProfile,
43
+ ProfileSource,
44
+ current_profile,
45
+ get_profile,
46
+ list_profiles,
47
+ read_auth_switch_config,
48
+ )
49
+ from cc_blackbox.sessions import (
50
+ ActiveSession,
51
+ SessionKind,
52
+ SessionStatus,
53
+ current_session,
54
+ find_session,
55
+ list_active_sessions,
56
+ )
57
+
58
+ __all__ = [
59
+ "ActiveSession",
60
+ "AmbiguousSession",
61
+ "AuthSwitchConfig",
62
+ "AuthSwitchConfigError",
63
+ "AuthSwitchProfile",
64
+ "ClaudeBlackboxError",
65
+ "ClaudeProfile",
66
+ "Compatibility",
67
+ "CompatibilityFeature",
68
+ "MessageTooLarge",
69
+ "MessagingError",
70
+ "MessagingTarget",
71
+ "MessagingUnavailable",
72
+ "Priority",
73
+ "ProfileNotFound",
74
+ "ProfileSource",
75
+ "RateLimitedSender",
76
+ "SUPPORTED_PEER_PROTOCOLS",
77
+ "SentMessage",
78
+ "SessionGone",
79
+ "SessionKind",
80
+ "SessionNotFound",
81
+ "SessionStatus",
82
+ "TESTED_CLAUDE_CODE_VERSIONS",
83
+ "UnsupportedProtocol",
84
+ "build_envelope",
85
+ "check_compatibility",
86
+ "claude_tmp_root",
87
+ "current_profile",
88
+ "current_session",
89
+ "find_session",
90
+ "get_profile",
91
+ "list_active_sessions",
92
+ "list_profiles",
93
+ "percent_encode_address",
94
+ "project_slug",
95
+ "read_auth_switch_config",
96
+ "rename_session",
97
+ "scratchpad_dir",
98
+ "send_message",
99
+ ]
100
+
101
+ __version__ = "0.2.0"