From aaefa3c26e6abd626388efa4199970bf11f043a1 Mon Sep 17 00:00:00 2001 From: Beon de Nood Date: Wed, 6 May 2026 20:58:20 -0400 Subject: [PATCH 1/2] feat: connect() falls back to CAPISCIO_API_KEY env var CapiscIO.connect() now reads api_key, agent_id, and server_url from environment variables when not passed explicitly: export CAPISCIO_API_KEY=sk_live_... agent = CapiscIO.connect() # just works This makes the zero-argument call the simplest path for users who have already set their env vars. from_env() still exists for the additional CAPISCIO_AGENT_NAME and CAPISCIO_DEV_MODE env vars. --- capiscio_sdk/connect.py | 45 +++++++++++++++++++++++++------------- tests/unit/test_connect.py | 8 +------ 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/capiscio_sdk/connect.py b/capiscio_sdk/connect.py index c873fd8..574acf5 100644 --- a/capiscio_sdk/connect.py +++ b/capiscio_sdk/connect.py @@ -263,11 +263,11 @@ class CapiscIO: @classmethod def connect( cls, - api_key: str, + api_key: Optional[str] = None, *, name: Optional[str] = None, agent_id: Optional[str] = None, - server_url: str = PROD_REGISTRY, + server_url: Optional[str] = None, keys_dir: Optional[Path] = None, auto_badge: bool = True, dev_mode: bool = False, @@ -285,10 +285,13 @@ def connect( 5. Sets up auto-renewal Args: - api_key: Your CapiscIO API key (sk_live_... or sk_test_...) + api_key: Your CapiscIO API key (sk_live_... or sk_test_...). + Falls back to CAPISCIO_API_KEY env var if not provided. name: Agent name (auto-generated if omitted) - agent_id: Specific agent ID to use (auto-discovered if omitted) - server_url: Registry server URL (default: production) + agent_id: Specific agent ID to use (auto-discovered if omitted). + Falls back to CAPISCIO_AGENT_ID env var if not provided. + server_url: Registry server URL. + Falls back to CAPISCIO_SERVER_URL env var, then production default. keys_dir: Directory for keys (default: ~/.capiscio/keys/{agent_id}/) auto_badge: Whether to automatically request a badge dev_mode: Use self-signed badges (Trust Level 0) @@ -299,10 +302,28 @@ def connect( AgentIdentity with full credentials and methods Example: + # Explicit API key agent = CapiscIO.connect(api_key="sk_live_abc123") + + # From environment (CAPISCIO_API_KEY) + agent = CapiscIO.connect() + print(f"Agent DID: {agent.did}") agent.emit("agent_started", {"version": "1.0"}) """ + if api_key is None: + api_key = os.environ.get("CAPISCIO_API_KEY") + if not api_key: + raise ValueError( + "api_key is required. Pass it directly or set the " + "CAPISCIO_API_KEY environment variable. " + "Get your API key at https://app.capisc.io" + ) + if agent_id is None: + agent_id = os.environ.get("CAPISCIO_AGENT_ID") + if server_url is None: + server_url = os.environ.get("CAPISCIO_SERVER_URL", PROD_REGISTRY) + connector = _Connector( api_key=api_key, name=name, @@ -329,19 +350,13 @@ def from_env(cls, **kwargs) -> AgentIdentity: - CAPISCIO_DEV_MODE (optional, default: false) - CAPISCIO_AGENT_PRIVATE_KEY_JWK (optional — JSON-encoded Ed25519 private JWK for ephemeral environments; printed on first generation) - """ - api_key = os.environ.get("CAPISCIO_API_KEY") - if not api_key: - raise ValueError( - "CAPISCIO_API_KEY environment variable is required. " - "Get your API key at https://app.capisc.io" - ) + Note: connect() also reads CAPISCIO_API_KEY, CAPISCIO_AGENT_ID, and + CAPISCIO_SERVER_URL from the environment when not passed explicitly. + from_env() additionally reads CAPISCIO_AGENT_NAME and CAPISCIO_DEV_MODE. + """ return cls.connect( - api_key=api_key, - agent_id=os.environ.get("CAPISCIO_AGENT_ID"), name=os.environ.get("CAPISCIO_AGENT_NAME"), - server_url=os.environ.get("CAPISCIO_SERVER_URL", PROD_REGISTRY), dev_mode=os.environ.get("CAPISCIO_DEV_MODE", "").lower() in ("true", "1", "yes"), **kwargs, ) diff --git a/tests/unit/test_connect.py b/tests/unit/test_connect.py index c2cf91a..8093b00 100644 --- a/tests/unit/test_connect.py +++ b/tests/unit/test_connect.py @@ -268,7 +268,7 @@ def test_from_env_requires_api_key(self): # Remove the key if it exists os.environ.pop("CAPISCIO_API_KEY", None) - with pytest.raises(ValueError, match="CAPISCIO_API_KEY environment variable is required"): + with pytest.raises(ValueError, match="CAPISCIO_API_KEY"): CapiscIO.from_env() def test_from_env_reads_env_vars(self): @@ -288,10 +288,7 @@ def test_from_env_reads_env_vars(self): CapiscIO.from_env() mock_connect.assert_called_once_with( - api_key="sk_test_env", - agent_id="env-agent-id", name="Env Agent", - server_url="https://env.server.com", dev_mode=True, ) @@ -308,10 +305,7 @@ def test_from_env_defaults(self): CapiscIO.from_env() mock_connect.assert_called_once_with( - api_key="sk_test_only", - agent_id=None, name=None, - server_url=PROD_REGISTRY, dev_mode=False, ) From 50da742b4164715b98141eddfb1f6c2d996f050c Mon Sep 17 00:00:00 2001 From: Beon de Nood Date: Thu, 7 May 2026 09:55:32 -0400 Subject: [PATCH 2/2] fix: handle empty CAPISCIO_SERVER_URL env var + add connect() env fallback tests - Empty/whitespace CAPISCIO_SERVER_URL now falls back to PROD_REGISTRY instead of passing empty string to httpx - Added 4 unit tests for connect() env var fallback behavior: reads from env, raises when missing, handles empty server_url, reads custom server_url --- capiscio_sdk/connect.py | 2 +- tests/unit/test_connect.py | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/capiscio_sdk/connect.py b/capiscio_sdk/connect.py index 574acf5..f04b78e 100644 --- a/capiscio_sdk/connect.py +++ b/capiscio_sdk/connect.py @@ -322,7 +322,7 @@ def connect( if agent_id is None: agent_id = os.environ.get("CAPISCIO_AGENT_ID") if server_url is None: - server_url = os.environ.get("CAPISCIO_SERVER_URL", PROD_REGISTRY) + server_url = os.environ.get("CAPISCIO_SERVER_URL") or PROD_REGISTRY connector = _Connector( api_key=api_key, diff --git a/tests/unit/test_connect.py b/tests/unit/test_connect.py index 8093b00..6b06c60 100644 --- a/tests/unit/test_connect.py +++ b/tests/unit/test_connect.py @@ -258,8 +258,42 @@ def test_connect_calls_connector(self): mock_connect.assert_called_once() assert result == mock_identity - -class TestCapiscIOFromEnv: + def test_connect_reads_api_key_from_env(self): + """Test connect() reads CAPISCIO_API_KEY from env when not passed.""" + with patch.dict(os.environ, {"CAPISCIO_API_KEY": "sk_from_env"}, clear=False): + with patch.object(_Connector, "__init__", return_value=None) as mock_init: + with patch.object(_Connector, "connect", return_value=MagicMock()): + CapiscIO.connect() + call_kwargs = mock_init.call_args[1] + assert call_kwargs["api_key"] == "sk_from_env" + + def test_connect_raises_without_api_key(self): + """Test connect() raises ValueError when no api_key and no env var.""" + with patch.dict(os.environ, {}, clear=False): + os.environ.pop("CAPISCIO_API_KEY", None) + with pytest.raises(ValueError, match="api_key is required"): + CapiscIO.connect() + + def test_connect_server_url_falls_back_to_prod(self): + """Test connect() uses PROD_REGISTRY when CAPISCIO_SERVER_URL is empty.""" + with patch.dict(os.environ, {"CAPISCIO_API_KEY": "sk_test", "CAPISCIO_SERVER_URL": ""}, clear=False): + with patch.object(_Connector, "__init__", return_value=None) as mock_init: + with patch.object(_Connector, "connect", return_value=MagicMock()): + CapiscIO.connect() + call_kwargs = mock_init.call_args[1] + assert call_kwargs["server_url"] == PROD_REGISTRY + + def test_connect_reads_server_url_from_env(self): + """Test connect() reads CAPISCIO_SERVER_URL from env.""" + with patch.dict(os.environ, { + "CAPISCIO_API_KEY": "sk_test", + "CAPISCIO_SERVER_URL": "https://custom.server.io", + }, clear=False): + with patch.object(_Connector, "__init__", return_value=None) as mock_init: + with patch.object(_Connector, "connect", return_value=MagicMock()): + CapiscIO.connect() + call_kwargs = mock_init.call_args[1] + assert call_kwargs["server_url"] == "https://custom.server.io" """Tests for CapiscIO.from_env() class method.""" def test_from_env_requires_api_key(self):