diff --git a/capiscio_sdk/connect.py b/capiscio_sdk/connect.py index c873fd8..f04b78e 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") or 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..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): @@ -268,7 +302,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 +322,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 +339,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, )