# ID Wispera Python SDK

Credential governance for AI agents - Python edition.

## Installation

```bash
pip install id-wispera
```

### With Framework Integrations

```bash
# LangChain support
pip install id-wispera[langchain]

# CrewAI support
pip install id-wispera[crewai]

# All integrations
pip install id-wispera[all]
```

## Quick Start

### Initialize a Vault

```bash
idw-py init
```

### Scan for Credentials

```bash
# Scan a directory
idw-py scan /path/to/project

# Scan system locations (OpenClaw format)
idw-py scan --system
```

### Import Credentials

```bash
# Scan and import all
idw-py import ./project --all --owner dev@company.com -y

# Import high-confidence only
idw-py import ./project --min-confidence 0.9 --owner dev@company.com

# Import single file
idw-py import .env --owner dev@company.com
```

#### Import Options

| Option | Description |
|--------|-------------|
| `--all` | Import all detected credentials from scan |
| `--min-confidence <level>` | Minimum confidence threshold (0-1) |
| `--format <format>` | Import format (env, json, openclaw) |
| `--owner <owner>` | Human owner email |
| `--auto-name` | Auto-generate passport names |
| `-y, --yes` | Import without confirmation |
| `-p, --path <path>` | Custom vault path |

### Manage Passports

```bash
# List all passports
idw-py list

# Show passport details
idw-py show <passport-id>

# Revoke a passport
idw-py revoke <passport-id> --reason "Compromised"
```

## Python API

### Basic Usage

```python
from id_wispera import Vault, create_passport, list_passports
from id_wispera.types import CreatePassportInput, CredentialType, VisaType, Platform

# Load vault
vault = Vault.load("~/.id-wispera/vault.json", passphrase="your-passphrase")

# Create a passport
passport = create_passport(vault, CreatePassportInput(
    name="openai-prod",
    agent_id="agent-001",
    credential_type=CredentialType.API_KEY,
    credential_value="sk-...",
    visa_type=VisaType.ACCESS,
    platforms=[Platform.OPENAI],
    scope=["chat", "embeddings"],
    human_owner="developer@example.com",
))

# List passports
passports = list_passports(vault, status="active")
for p in passports:
    print(f"{p.name}: {p.status}")
```

### Credential Detection

```python
from id_wispera.detection import detect_credentials, mask_credentials_in_text

# Detect credentials in text
text = "My API key is sk-abc123...xyz789"
results = detect_credentials(text)

for result in results:
    print(f"Found {result.type} at line {result.line}")
    print(f"  Value: {result.value[:10]}...")
    print(f"  Confidence: {result.confidence}")

# Mask credentials for safe logging
safe_text = mask_credentials_in_text(text)
print(safe_text)  # "My API key is [REDACTED]"
```

### Policy Engine

```python
from id_wispera.policy import evaluate_policy, validate_passport

# Evaluate whether a passport is allowed to perform an action
result = evaluate_policy(passport, action="access")
print(f"Allowed: {result.allowed}, Reason: {result.reason}")

# Validate a passport against all configured policies
validation = validate_passport(passport)
print(f"Valid: {validation.valid}, Violations: {validation.violations}")
```

### Delegation Management

```python
from id_wispera.delegation import add_delegation, get_delegation_depth

# Add a delegation hop to a passport
delegated = add_delegation(vault, passport_id, hop, issued_by="admin")
print(f"Delegated passport: {delegated.id}")

# Check delegation chain depth
depth = get_delegation_depth(vault, passport_id)
print(f"Delegation depth: {depth}")
```

### Secure Sharing

```python
from id_wispera.sharing import create_share_link, resolve_share_link

# Create a share link with expiry and view limits
link = create_share_link(vault, passport_id, expires_in=3600, max_views=1)
print(f"Share URL: {link.url}")

# Resolve a share link to retrieve the passport
shared = resolve_share_link(vault, link.token)
print(f"Shared passport: {shared.name}")
```

### Locations Registry

```python
from id_wispera.locations import detect_installed_providers, get_provider

# Detect which credential providers are available on the system
providers = detect_installed_providers()
for p in providers:
    print(f"Provider: {p.name} ({p.provider_type})")

# Get a specific provider by name
provider = get_provider("aws")
print(f"Provider: {provider.name}, Installed: {provider.installed}")
```

### Provisioning

```python
from id_wispera.provisioning import provision_and_create_passport, list_providers

# List available provisioning providers
providers = list_providers()
for p in providers:
    print(f"{p.name}: {p.description}")

# Provision a new credential and create a passport in one step
passport = provision_and_create_passport(vault, request, auth)
print(f"Provisioned: {passport.name} on {passport.platforms}")
```

## Framework Integrations

### LangChain

```python
from id_wispera.integrations.langchain import WisperaCredentialProvider
from langchain_openai import ChatOpenAI

# Initialize provider (authenticates via session token or OS keychain automatically)
provider = WisperaCredentialProvider(vault_path="~/.id-wispera")

# Use with LangChain
llm = ChatOpenAI(api_key=provider.get("openai-prod"))

# Or use convenience methods
llm = ChatOpenAI(api_key=provider.get_openai_key())
```

> **Deprecated**: The `passphrase` parameter in the `WisperaCredentialProvider` constructor is deprecated. Authentication is now resolved automatically via `IDW_SESSION_TOKEN` or OS keychain. Run `idw auth login` before first use.

### CrewAI

```python
from id_wispera.integrations.crewai import WisperaSecretManager, WisperaToolCredentials
from crewai import Agent, Task, Crew

# Initialize secret manager (authenticates via session token or OS keychain)
secrets = WisperaSecretManager()

# Create agent with governed credentials
researcher = Agent(
    role="Research Analyst",
    goal="Research and analyze information",
    llm_config=secrets.get_llm_config("anthropic-research", model="claude-3-5-sonnet-20241022"),
)

# Use tool credentials — retrieve via creds.get() instead of os.environ injection
from crewai_tools import SerperDevTool

creds = WisperaToolCredentials(secrets, "serper")
search_tool = SerperDevTool(api_key=creds.get())
```

> **Deprecated**: The `passphrase` parameter in the `WisperaSecretManager` constructor is deprecated. Authentication is now resolved automatically via `IDW_SESSION_TOKEN` or OS keychain.
>
> **Breaking change**: `WisperaToolCredentials` no longer injects credentials into `os.environ`. Use `creds.get()` to retrieve the credential value directly.

### OpenAI Agents SDK

```python
from id_wispera.integrations.openai_agents import WisperaOpenAIAgentProvider

provider = WisperaOpenAIAgentProvider()

# Get API key for agent initialization
api_key = provider.get_agent_key("openai-prod")

# Get full agent config (api_key, model, provider)
config = provider.get_agent_config("openai-prod", model="gpt-4")

# Get tool authentication headers
tool_auth = provider.get_tool_auth("serper-api")
# Returns: {"tool": "serper-api", "credential": "...", "header_name": "Authorization", "header_template": "Bearer ..."}

# Scoped handoff credentials
cred = provider.get_handoff_credential("openai-prod", target_agent="research-agent", allowed_scopes=["read"])

# Batch tool credentials
creds = provider.get_tool_credentials({"search": "serper-api", "weather": "weather-api"})
```

### Google A2A Protocol

```python
from id_wispera.integrations.google_a2a import WisperaA2AProvider

provider = WisperaA2AProvider()

# Populate an Agent Card with credentials
creds = provider.get_agent_credentials("my-agent")
# Returns: {"auth_token": "...", "agent_id": "...", "scopes": [...], "expires_at": "..."}

# Validate incoming agent authentication
result = provider.validate_agent_auth(token="...", expected_agent_id="agent-123")
# Returns: {"valid": True, "agent_id": "agent-123"}

# Task-scoped credential access
task_creds = provider.get_task_credentials("task-456", required_scopes=["read"], passport_names=["my-agent"])
```

### Slack

```python
from id_wispera.integrations.slack import WisperaSlackProvider

provider = WisperaSlackProvider()

# Individual tokens
bot_token = provider.get_bot_token("slack-bot")       # xoxb-...
app_token = provider.get_app_token("slack-app")        # xapp-...
user_token = provider.get_user_token("slack-user")     # xoxp-...
webhook_url = provider.get_webhook_url("slack-webhook")

# Typed credential with auto-detection
cred = provider.get_slack_credential("slack-bot")
# Returns: {"value": "xoxb-...", "token_type": "bot", "team_id": "T12345"}

# Socket Mode (bot + app tokens together)
tokens = provider.get_socket_mode_tokens("slack-bot", "slack-app")
# Returns: {"bot_token": "xoxb-...", "app_token": "xapp-..."}

# List all Slack credentials in the vault
names = provider.list_slack_credentials()
```

## Auth Module

The `id_wispera.auth` module provides the zero-plaintext credential architecture for Python:

```python
from id_wispera.auth import PassphraseProvider, SessionTokenManager, KeychainProvider

# PassphraseProvider — derive vault key from passphrase (interactive login)
pp = PassphraseProvider()
vault_key = pp.derive_key(passphrase)

# KeychainProvider — cache/retrieve vault key from OS keychain
kc = KeychainProvider()
kc.store(vault_key)
cached = kc.retrieve()

# SessionTokenManager — create, validate, revoke scoped session tokens
stm = SessionTokenManager(vault)
token = stm.create(name="ci-deploy", scope=["read", "list"], ttl="24h")
session = stm.validate(token)
stm.revoke(token.id)
tokens = stm.list()
```

| Class | Purpose |
|-------|---------|
| `PassphraseProvider` | Derive vault key from passphrase (interactive login) |
| `SessionTokenManager` | Create / validate / revoke / list scoped session tokens |
| `KeychainProvider` | Cache vault key in the OS keychain for session persistence |

## Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `IDW_SESSION_TOKEN` | Scoped session token (recommended for headless/CI) | |
| `IDW_VAULT_PATH` | Vault directory path | `~/.id-wispera` |
| `IDW_PASSPHRASE` | Vault passphrase | **Deprecated** -- use `idw auth login` or `IDW_SESSION_TOKEN` |

## Vault Compatibility

The Python SDK uses the same vault format as the TypeScript SDK. Vaults are interoperable:

- AES-256-GCM encryption
- Scrypt key derivation (N=16384, r=8, p=1)
- JSON storage format

## Development

```bash
# Install in development mode
pip install -e ".[dev]"

# Run tests
pytest

# Type checking
mypy id_wispera
```

## License

MIT
