diff --git a/stripe/__init__.py b/stripe/__init__.py index dd958d504..6f2ad0969 100644 --- a/stripe/__init__.py +++ b/stripe/__init__.py @@ -107,6 +107,10 @@ def set_app_info( } +from stripe._util import _emit_claude_code_hint + +_emit_claude_code_hint() + # The beginning of the section generated from our OpenAPI spec from importlib import import_module diff --git a/stripe/_util.py b/stripe/_util.py index 5a17b3932..386894080 100644 --- a/stripe/_util.py +++ b/stripe/_util.py @@ -389,3 +389,20 @@ def _wrapper(*args, **kwargs): return class_method(*args, **kwargs) return _wrapper + + +# If we detect environment variables that indicate we're running in a Claude Code +# session, emit a hint to the parent process so it can load the plugin. +# https://code.claude.com/docs/en/plugin-hints +def claude_code_hint_line() -> str: + return '\n' + + +def _emit_claude_code_hint() -> None: + try: + if os.environ.get("CLAUDECODE") or os.environ.get( + "CLAUDE_CODE_CHILD_SESSION" + ): + sys.stderr.write(claude_code_hint_line()) + except Exception: + pass diff --git a/tests/test_exports.py b/tests/test_exports.py index 44f11846d..3aff0a530 100644 --- a/tests/test_exports.py +++ b/tests/test_exports.py @@ -4,6 +4,8 @@ import subprocess import sys +from stripe._util import claude_code_hint_line + def assert_output(code: str, expected: str) -> None: process = subprocess.Popen( @@ -14,6 +16,7 @@ def assert_output(code: str, expected: str) -> None: stdout, stderr = process.communicate() + stderr = stderr.replace(claude_code_hint_line().encode(), b"") assert not stderr, f"Error: {stderr.decode()}" output = stdout.decode().strip() diff --git a/tests/test_util.py b/tests/test_util.py index 1331dbe20..a8dc01c80 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,3 +1,5 @@ +import io +import os import sys from collections import namedtuple @@ -11,6 +13,8 @@ log_info, log_debug, sanitize_id, + claude_code_hint_line, + _emit_claude_code_hint, ) from stripe import Balance from stripe._api_mode import ApiMode @@ -178,3 +182,36 @@ def test_sanitize_id(self): ) def test_get_api_mode(self, url: str, expected: ApiMode): assert get_api_mode(url) == expected + + +class TestEmitClaudeCodeHint: + _HINT = claude_code_hint_line() + + def _capture(self, env_vars: dict) -> str: + buf = io.StringIO() + original = os.environ.copy() + try: + for k in ("CLAUDECODE", "CLAUDE_CODE_CHILD_SESSION"): + os.environ.pop(k, None) + os.environ.update(env_vars) + old_stderr, sys.stderr = sys.stderr, buf + try: + _emit_claude_code_hint() + finally: + sys.stderr = old_stderr + finally: + os.environ.clear() + os.environ.update(original) + return buf.getvalue() + + def test_emits_when_CLAUDECODE_set(self): + assert self._capture({"CLAUDECODE": "1"}) == self._HINT + + def test_emits_when_CLAUDE_CODE_CHILD_SESSION_set(self): + assert ( + self._capture({"CLAUDE_CODE_CHILD_SESSION": "session-id"}) + == self._HINT + ) + + def test_no_emit_without_env_vars(self): + assert self._capture({}) == ""