Claude Skill

configuring

Universal environment variable loader for AI agent environments. Loads secrets and config from Claude.ai, Claude Code, OpenAI Codex, Jules, and standard .env files.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download oaustegard-claude-skills-plugins_environment-and-config_skills_configuring-e39c726.zip · 11 KB
Part of oaustegard/claude-skills — 39 skills

Install

skills CLI npx skills add https://github.com/oaustegard/claude-skills/tree/main/plugins/environment-and-config/skills/configuring
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install oaustegard-claude-skills@llmmart
Git git clone https://github.com/oaustegard/claude-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole oaustegard/claude-skills collection as a plugin from our marketplace. Git is the plain clone.

README

configuring

Universal configuration management for AI agent environments. Loads environment variables, secrets, and config from Claude.ai, Claude Code, OpenAI Codex, Jules, and standard .env files.

Skill manifest

Configuring

Unified configuration management across AI coding environments. Load environment variables, secrets, and other opinionated configuration setups from any AI coding platform.

Quick Start

import sys
sys.path.insert(0, '/path/to/claude-skills')  # or wherever skills are installed
from configuring import get_env, detect_environment

# Get a variable (searches all sources automatically)
token = get_env("TURSO_TOKEN", required=True)

# With default
port = get_env("PORT", default="8080")

# What environment are we in?
env = detect_environment()  # "claude.ai", "claude-code-desktop", "codex", "jules", etc.

Supported Environments

Environment Config Sources
Claude.ai Projects /mnt/project/*.env, /mnt/project/*-token.txt
Claude Code ~/.claude/settings.json (env block), .claude/settings.json
OpenAI Codex ~/.codex/config.toml, setup script → ~/.bashrc, shell_snapshots/*.sh
Jules Environment settings UI, .env in repo
Universal os.environ, .env, .env.local

API Reference

# Core
get_env(key, default=None, *, required=False, validator=None) -> str | None
load_env(path) -> dict[str, str]           # Load specific file
load_all(force_reload=False) -> dict       # Load all sources

# Utilities
detect_environment() -> str                 # Current platform
mask_secret(value, show_chars=4) -> str    # Safe logging
debug_info() -> dict                        # Troubleshooting
get_loaded_sources() -> list[str]          # What was checked

Credential File Formats

.env files (KEY=value):

TURSO_TOKEN=eyJhbGciOiJFZERTQSI...
EMBEDDING_API_KEY=sk-svcacct-...

Single-value files (*-token.txt, *-key.txt):

eyJhbGciOiJFZERTQSI...

Filename becomes key: turso-token.txt → TURSO_TOKEN

Claude Code settings.json:

{
  "env": {
    "TURSO_TOKEN": "eyJhbGciOiJFZERTQSI..."
  }
}

Priority Order

Later sources override earlier:

  1. OS environment variables
  2. Platform-specific sources (detected automatically)
  3. .env files in cwd
  4. OS environment variables (again - explicit exports always win)

Debugging

import sys
sys.path.insert(0, '/path/to/claude-skills')
from configuring import debug_info
print(debug_info())
# {'environment': 'claude.ai', 'sources': ['os.environ', 'claude.ai:/mnt/project/'], ...}

CLI:

cd /path/to/claude-skills/configuring
python scripts/getting_env.py                    # Show debug info
python scripts/getting_env.py TURSO_TOKEN        # Get specific key

Migration from api-credentials / getting-env

Replace:

# Old (api-credentials)
from credentials import get_anthropic_api_key
key = get_anthropic_api_key()

# Old (getting-env)
from getting_env import get_env
key = get_env("ANTHROPIC_API_KEY")

# New (configuring)
import sys
sys.path.insert(0, '/path/to/claude-skills')
from configuring import get_env
key = get_env("ANTHROPIC_API_KEY", required=True)
Files (claude-skills)
  • assets
    • example.env 485 B · in bundle
  • examples
    • turso_refactored.py 8.9 KB
      """
      Turso HTTP API layer for remembering skill.
      
      This module handles:
      - Credential initialization via configuring skill
      - HTTP request retry logic (_retry_with_backoff)
      - SQL execution via Turso HTTP API (_exec, _exec_batch)
      - JSON field parsing (_parse_memory_row)
      
      Imports from: state, configuring
      """
      
      import json
      import os
      import sys
      import time
      
      import requests
      
      from . import state
      
      # Import from configuring skill (must be on Python path)
      try:
          sys.path.insert(0, '/path/to/claude-skills')  # Adjust path as needed
          from configuring import detect_environment, get_env
      except ImportError:
          # Fallback if configuring skill not available
          from pathlib import Path
      
          def _fallback_get_env(key, default=None, required=False):
              """Minimal fallback if configuring skill not installed."""
              value = os.environ.get(key)
              if not value:
                  # Try /mnt/project/*.env
                  for env_file in Path("/mnt/project").glob("*.env"):
                      for line in env_file.read_text().splitlines():
                          if line.strip().startswith(f"{key}="):
                              value = line.partition("=")[2].strip()
                              break
              if required and not value:
                  raise ValueError(f"Required environment variable '{key}' not found")
              return value or default
          
          get_env = _fallback_get_env
          detect_environment = lambda: "unknown"
      
      
      def _init():
          """Lazy-load credentials and URL using getting-env skill."""
          if state._TOKEN is None:
              # Load TURSO_URL (with default)
              state._URL = get_env("TURSO_URL", default=state._DEFAULT_URL)
      
              # Load TURSO_TOKEN (required)
              try:
                  state._TOKEN = get_env("TURSO_TOKEN", required=True)
              except ValueError as e:
                  raise RuntimeError(str(e)) from e
      
              # Clean token: remove any whitespace
              if state._TOKEN:
                  state._TOKEN = state._TOKEN.strip().replace(" ", "")
      
              state._HEADERS = {"Authorization": f"Bearer {state._TOKEN}", "Content-Type": "application/json"}
      
      
      def _retry_with_backoff(fn, max_retries=3, base_delay=1.0):
          """Retry a function with exponential backoff on transient errors.
      
          Args:
              fn: Callable that may raise exceptions
              max_retries: Maximum number of retry attempts (default 3)
              base_delay: Initial delay in seconds (default 1.0)
      
          Returns:
              Result of fn() if successful
      
          Raises:
              Last exception if all retries exhausted
          """
          for attempt in range(max_retries):
              try:
                  return fn()
              except Exception as e:
                  if attempt == max_retries - 1:
                      # Last attempt failed, re-raise
                      raise
                  # Check if it's a retriable error (503, 429, SSL handshake failures)
                  error_str = str(e)
                  is_retriable = (
                      '503' in error_str or
                      '429' in error_str or
                      'Service Unavailable' in error_str or
                      'SSL' in error_str or
                      'SSLError' in error_str or
                      'HANDSHAKE_FAILURE' in error_str
                  )
                  if is_retriable:
                      delay = base_delay * (2 ** attempt)
                      print(f"Warning: API request failed (attempt {attempt + 1}/{max_retries}), retrying in {delay}s: {e}")
                      time.sleep(delay)
                  else:
                      # Non-retriable error, fail immediately
                      raise
      
      
      def _parse_memory_row(row: dict) -> dict:
          """Parse JSON fields in a memory row (tags, entities, refs).
      
          Args:
              row: Raw row dict from database
      
          Returns:
              Row dict with parsed JSON fields
          """
          # Parse tags field
          if 'tags' in row and row['tags'] is not None:
              if isinstance(row['tags'], str):
                  try:
                      row['tags'] = json.loads(row['tags'])
                  except json.JSONDecodeError:
                      row['tags'] = []
      
          # Parse entities field
          if 'entities' in row and row['entities'] is not None:
              if isinstance(row['entities'], str):
                  try:
                      row['entities'] = json.loads(row['entities'])
                  except json.JSONDecodeError:
                      row['entities'] = []
      
          # Parse refs field
          if 'refs' in row and row['refs'] is not None:
              if isinstance(row['refs'], str):
                  try:
                      row['refs'] = json.loads(row['refs'])
                  except json.JSONDecodeError:
                      row['refs'] = []
      
          return row
      
      
      def _exec_batch(statements: list) -> list:
          """Execute multiple SQL statements in a single pipeline request.
      
          Args:
              statements: List of SQL strings or (sql, args) tuples
      
          Returns:
              List of result lists (one per statement)
      
          Example:
              results = _exec_batch([
                  "SELECT * FROM config WHERE category = 'profile'",
                  ("SELECT * FROM memories WHERE type = ?", ["decision"])
              ])
              profile_data = results[0]
              decisions = results[1]
          """
          _init()
          requests_list = []
      
          for stmt in statements:
              if isinstance(stmt, tuple):
                  sql, args = stmt
              else:
                  sql, args = stmt, []
      
              request = {"type": "execute", "stmt": {"sql": sql}}
              if args:
                  request["stmt"]["args"] = [
                      {"type": "text", "value": str(v)} if v is not None else {"type": "null"}
                      for v in args
                  ]
              requests_list.append(request)
      
          # Add close request
          requests_list.append({"type": "close"})
      
          try:
              resp = requests.post(
                  f"{state._URL}/v2/pipeline",
                  headers=state._HEADERS,
                  json={"requests": requests_list},
                  timeout=30
              ).json()
          except requests.exceptions.SSLError as e:
              raise RuntimeError(
                  f"SSL error connecting to Turso database. This often indicates missing or invalid credentials.\n"
                  f"Check that TURSO_TOKEN is set correctly.\n"
                  f"Original error: {e}"
              ) from e
          except requests.exceptions.RequestException as e:
              raise RuntimeError(
                  f"Network error connecting to Turso database at {state._URL}\n"
                  f"Check network connectivity and credentials (TURSO_TOKEN).\n"
                  f"Original error: {e}"
              ) from e
      
          # Parse results (exclude the close response)
          results = []
          for r in resp.get("results", [])[:-1]:  # Exclude close result
              if r["type"] != "ok":
                  error_msg = r.get("error", {}).get("message", "Unknown error")
                  error_code = r.get("error", {}).get("code", "UNKNOWN")
                  raise RuntimeError(f"Database error [{error_code}]: {error_msg}")
      
              res = r["response"]["result"]
              cols = [c["name"] for c in res["cols"]]
              rows = [
                  {cols[i]: (row[i].get("value") if row[i].get("type") != "null" else None)
                   for i in range(len(cols))}
                  for row in res["rows"]
              ]
      
              # Parse JSON fields if this is a memory query
              if rows and 'tags' in rows[0]:
                  rows = [_parse_memory_row(row) for row in rows]
      
              results.append(rows)
      
          return results
      
      
      def _exec(sql, args=None, parse_json: bool = True):
          """Execute SQL, return list of dicts.
      
          Args:
              sql: SQL query
              args: Query arguments
              parse_json: If True, parse JSON fields (tags, entities, refs) in memory rows
          """
          _init()
          stmt = {"sql": sql}
          if args:
              stmt["args"] = [
                  {"type": "text", "value": str(v)} if v is not None else {"type": "null"}
                  for v in args
              ]
      
          try:
              resp = requests.post(
                  f"{state._URL}/v2/pipeline",
                  headers=state._HEADERS,
                  json={"requests": [{"type": "execute", "stmt": stmt}]},
                  timeout=30
              ).json()
          except requests.exceptions.SSLError as e:
              raise RuntimeError(
                  f"SSL error connecting to Turso database. This often indicates missing or invalid credentials.\n"
                  f"Check that TURSO_TOKEN is set correctly.\n"
                  f"Original error: {e}"
              ) from e
          except requests.exceptions.RequestException as e:
              raise RuntimeError(
                  f"Network error connecting to Turso database at {state._URL}\n"
                  f"Check network connectivity and credentials (TURSO_TOKEN).\n"
                  f"Original error: {e}"
              ) from e
      
          r = resp["results"][0]
          if r["type"] != "ok":
              error_msg = r.get("error", {}).get("message", "Unknown error")
              error_code = r.get("error", {}).get("code", "UNKNOWN")
              raise RuntimeError(f"Database error [{error_code}]: {error_msg}")
      
          res = r["response"]["result"]
          cols = [c["name"] for c in res["cols"]]
          rows = [
              {cols[i]: (row[i].get("value") if row[i].get("type") != "null" else None) for i in range(len(cols))}
              for row in res["rows"]
          ]
      
          # Parse JSON fields if this is a memory query
          if parse_json and rows and 'tags' in rows[0]:
              rows = [_parse_memory_row(row) for row in rows]
      
          return rows
      
  • scripts
    • getting_env.py 19.4 KB
      #!/usr/bin/env python3
      """
      Universal Configuration Management for AI Agent Environments
      
      Loads environment variables, secrets, and configuration from any supported AI coding environment:
      - Claude.ai Projects (project knowledge files)
      - Claude Code (desktop & web)
      - OpenAI Codex
      - Jules (Google)
      - Standard environments (.env files, shell env vars)
      
      Usage:
          import sys
          sys.path.insert(0, '/path/to/claude-skills')
          from configuring import get_env, load_env, detect_environment
      
          # Get a single variable (searches all sources)
          api_key = get_env("MY_API_KEY")
      
          # With default
          api_key = get_env("MY_API_KEY", default="")
      
          # Detect current environment
          env_type = detect_environment()  # "claude.ai", "claude-code", "codex", "jules", "unknown"
      
          # Bulk load from a specific file
          vars = load_env("/path/to/.env")
      """
      
      import json
      import os
      from collections.abc import Callable
      from pathlib import Path
      from typing import Any
      
      __version__ = "2.0.0"
      
      # Module-level cache
      _cache: dict[str, str] = {}
      _loaded_sources: list[str] = []
      
      
      # =============================================================================
      # Environment Detection
      # =============================================================================
      
      # @lat: [[infrastructure#Environment Detection]]
      def detect_environment() -> str:
          """
          Detect which AI agent environment we're running in.
          
          Returns:
              One of: "claude.ai", "claude-code-desktop", "claude-code-web", 
                      "codex", "jules", "unknown"
          """
          # Claude.ai Projects: /mnt/project exists and is populated
          if Path("/mnt/project").is_dir():
              # Claude.ai web chat has /mnt/skills and /mnt/user-data
              if Path("/mnt/skills").exists() and Path("/mnt/user-data").exists():
                  return "claude.ai"
          
          # Claude Code: ~/.claude directory exists with settings
          claude_home = Path.home() / ".claude"
          if claude_home.is_dir():
              if (claude_home / "settings.json").exists():
                  return "claude-code-desktop"
          
          # OpenAI Codex: ~/.codex directory or CODEX_HOME env var
          codex_home = os.environ.get("CODEX_HOME", str(Path.home() / ".codex"))
          if Path(codex_home).is_dir():
              return "codex"
          
          # Jules: /home/jules is the standard user
          if Path("/home/jules").exists() or os.environ.get("JULES_SESSION_ID"):
              return "jules"
          
          # Claude Code on web may not have ~/.claude but has container markers
          if os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("CLAUDE_CODE"):
              return "claude-code-web"
          
          return "unknown"
      
      
      # =============================================================================
      # File Parsing Utilities
      # =============================================================================
      
      def _parse_env_file(path: Path) -> dict[str, str]:
          """
          Parse a .env file into a dict.
          
          Handles:
          - KEY=value
          - KEY="value with spaces"
          - KEY='value with spaces'
          - export KEY=value
          - # comments
          - Empty lines
          
          Args:
              path: Path to .env file
              
          Returns:
              Dict of key-value pairs
          """
          env = {}
          if not path.exists():
              return env
          
          for line in path.read_text().splitlines():
              line = line.strip()
              
              # Skip empty lines and comments
              if not line or line.startswith('#'):
                  continue
              
              # Handle 'export KEY=value' format
              line = line.removeprefix('export ')
              
              # Must have an equals sign
              if '=' not in line:
                  continue
              
              key, _, value = line.partition('=')
              key = key.strip()
              value = value.strip()
              
              # Remove surrounding quotes
              if (value.startswith('"') and value.endswith('"')) or \
                 (value.startswith("'") and value.endswith("'")):
                  value = value[1:-1]
              
              env[key] = value
          
          return env
      
      
      def _parse_shell_exports(path: Path) -> dict[str, str]:
          """
          Parse exported environment variables from a shell script or snapshot.
      
          Handles:
          - export KEY=value
          - declare -x KEY=value
      
          Args:
              path: Path to a shell file
      
          Returns:
              Dict of key-value pairs
          """
          env = {}
          if not path.exists():
              return env
      
          for line in path.read_text().splitlines():
              line = line.strip()
              if line.startswith("export "):
                  line = line[len("export "):]
              elif line.startswith("declare -x "):
                  line = line[len("declare -x "):]
              else:
                  continue
      
              if '=' not in line:
                  continue
      
              key, _, value = line.partition('=')
              key = key.strip()
              value = value.strip()
      
              if (value.startswith('"') and value.endswith('"')) or \
                 (value.startswith("'") and value.endswith("'")):
                  value = value[1:-1]
      
              env[key] = value
      
          return env
      
      
      def _parse_single_value_file(path: Path, key_name: str) -> dict[str, str]:
          """
          Parse a single-value file (like turso-token.txt) into a dict.
          
          Handles:
          - Plain value
          - KEY=value format (extracts just the value)
          
          Args:
              path: Path to the file
              key_name: Key name to use if file contains just a value
              
          Returns:
              Dict with the key-value pair
          """
          if not path.exists():
              return {}
          
          content = path.read_text().strip()
          
          # Check if it's already in KEY=value format
          if '=' in content and '\n' not in content:
              key, _, value = content.partition('=')
              return {key.strip(): value.strip()}
          
          # Plain value
          return {key_name: content}
      
      
      def _parse_json_settings(path: Path) -> dict[str, str]:
          """
          Parse env vars from a Claude Code settings.json file.
          
          Looks for the "env" block:
          {
              "env": {
                  "MY_VAR": "value"
              }
          }
          
          Args:
              path: Path to settings.json
              
          Returns:
              Dict of env vars from the "env" block
          """
          if not path.exists():
              return {}
          
          try:
              data = json.loads(path.read_text())
              return data.get("env", {})
          except (OSError, json.JSONDecodeError):
              return {}
      
      
      def _parse_toml_env(path: Path) -> dict[str, str]:
          """
          Parse environment variables from a Codex config.toml file.
          
          Looks for shell_environment_policy.set block.
          Basic TOML parsing - doesn't handle all TOML features.
          
          Args:
              path: Path to config.toml
              
          Returns:
              Dict of env vars
          """
          if not path.exists():
              return {}
          
          env = {}
          try:
              content = path.read_text()
              # Simple parsing for common patterns
              # Look for set = { KEY = "value", ... }
              import re
              
              # Find shell_environment_policy.set or [shell_environment_policy] set = 
              match = re.search(r'set\s*=\s*\{([^}]+)\}', content)
              if match:
                  pairs = match.group(1)
                  for pair_match in re.finditer(r'(\w+)\s*=\s*"([^"]*)"', pairs):
                      env[pair_match.group(1)] = pair_match.group(2)
          except OSError:
              pass
          
          return env
      
      
      # =============================================================================
      # Source Loading Functions
      # =============================================================================
      
      def _load_os_environ() -> dict[str, str]:
          """Load all environment variables from os.environ."""
          return dict(os.environ)
      
      
      def _load_claude_ai_project() -> dict[str, str]:
          """
          Load environment variables from Claude.ai project knowledge.
          
          Searches /mnt/project for:
          - *.env files (KEY=value format)
          - *-token.txt, *-key.txt, *-secret.txt files (single value)
          """
          env = {}
          project_dir = Path("/mnt/project")
          
          if not project_dir.is_dir():
              return env
          
          # Load all .env files
          for env_file in project_dir.glob("*.env"):
              env.update(_parse_env_file(env_file))
          
          # Load single-value credential files
          patterns = ["*-token.txt", "*-key.txt", "*-secret.txt", "*_TOKEN.txt", "*_KEY.txt"]
          for pattern in patterns:
              for cred_file in project_dir.glob(pattern):
                  # Derive key name from filename
                  # e.g., "turso-token.txt" -> "TURSO_TOKEN"
                  stem = cred_file.stem.upper().replace("-", "_")
                  env.update(_parse_single_value_file(cred_file, stem))
          
          return env
      
      
      def _load_claude_code() -> dict[str, str]:
          """
          Load environment variables from Claude Code settings.
          
          Searches:
          - ~/.claude/settings.json (user settings)
          - ~/.claude/settings.local.json (user local settings)  
          - .claude/settings.json (project settings)
          - .claude/settings.local.json (project local settings)
          """
          env = {}
          
          # User settings (lower priority)
          user_claude = Path.home() / ".claude"
          for filename in ["settings.json", "settings.local.json"]:
              env.update(_parse_json_settings(user_claude / filename))
          
          # Project settings (higher priority)
          project_claude = Path.cwd() / ".claude"
          for filename in ["settings.json", "settings.local.json"]:
              env.update(_parse_json_settings(project_claude / filename))
          
          return env
      
      
      def _load_codex() -> dict[str, str]:
          """
          Load environment variables from OpenAI Codex configuration.
          
          Searches:
          - $CODEX_HOME/config.toml or ~/.codex/config.toml
          - .codex/config.toml (project)
          """
          env = {}
          
          # User config
          codex_home = Path(os.environ.get("CODEX_HOME", str(Path.home() / ".codex")))
          env.update(_parse_toml_env(codex_home / "config.toml"))
          
          # Shell setup exports (~/.bashrc, ~/.profile)
          for path in [Path.home() / ".bashrc", Path.home() / ".profile"]:
              env.update(_parse_shell_exports(path))
      
          # Shell snapshot exports (used by Codex)
          snapshot_dir = codex_home / "shell_snapshots"
          if snapshot_dir.is_dir():
              snapshots = sorted(snapshot_dir.glob("*.sh"), key=lambda p: p.stat().st_mtime)
              if snapshots:
                  env.update(_parse_shell_exports(snapshots[-1]))
      
          # Project config (higher priority)
          env.update(_parse_toml_env(Path.cwd() / ".codex" / "config.toml"))
          
          return env
      
      
      def _load_jules() -> dict[str, str]:
          """
          Load environment variables for Jules environment.
          
          Jules primarily uses:
          - Standard environment variables set in Jules config UI
          - .env files in repo root
          """
          env = {}
          
          # Jules mostly relies on env vars set via UI, but check .env in common locations
          for path in [Path.cwd() / ".env", Path.home() / ".env"]:
              env.update(_parse_env_file(path))
          
          return env
      
      
      def _load_dotenv_files() -> dict[str, str]:
          """
          Load environment variables from standard .env file locations.
          
          Searches (in order, later overrides earlier):
          - ~/.env
          - ./.env
          - ./.env.local
          """
          env = {}
          
          paths = [
              Path.home() / ".env",
              Path.cwd() / ".env",
              Path.cwd() / ".env.local",
          ]
          
          for path in paths:
              env.update(_parse_env_file(path))
          
          return env
      
      
      # =============================================================================
      # Main API
      # =============================================================================
      
      # @lat: [[infrastructure#Environment Detection]]
      def load_all(force_reload: bool = False) -> dict[str, str]:
          """
          Load environment variables from all detected sources.
          
          Sources are loaded in priority order (later sources override earlier):
          1. OS environment variables (lowest - can be overridden)
          2. Platform-specific sources (Claude.ai, Claude Code, Codex, Jules)
          3. Standard .env files
          4. OS environment variables again (highest - explicit env vars win)
          
          Results are cached. Use force_reload=True to refresh.
          
          Args:
              force_reload: If True, clear cache and reload from all sources
              
          Returns:
              Dict of all loaded environment variables
          """
          global _cache, _loaded_sources
          
          if _cache and not force_reload:
              return _cache.copy()
          
          _cache.clear()
          _loaded_sources.clear()
          
          env_type = detect_environment()
          
          # Base: OS environment (can be overridden by file configs)
          _cache.update(_load_os_environ())
          _loaded_sources.append("os.environ")
          
          # Platform-specific sources
          if env_type == "claude.ai":
              _cache.update(_load_claude_ai_project())
              _loaded_sources.append("claude.ai:/mnt/project/")
          
          if env_type in ("claude-code-desktop", "claude-code-web"):
              _cache.update(_load_claude_code())
              _loaded_sources.append("claude-code:~/.claude/ + .claude/")
          
          if env_type == "codex":
              _cache.update(_load_codex())
              _loaded_sources.append("codex:config.toml + shell exports + shell_snapshots")
          
          if env_type == "jules":
              _cache.update(_load_jules())
              _loaded_sources.append("jules:.env")
          
          # Standard .env files (can override platform sources)
          dotenv_vars = _load_dotenv_files()
          if dotenv_vars:
              _cache.update(dotenv_vars)
              _loaded_sources.append("dotenv:.env files")
          
          # OS environ again - explicit env vars should win over all file sources
          # This ensures `export VAR=x && python ...` works as expected
          _cache.update(_load_os_environ())
          
          return _cache.copy()
      
      
      def get_env(
          key: str,
          default: str | None = None,
          *,
          required: bool = False,
          validator: Callable[[str], bool] | None = None,
      ) -> str | None:
          """
          Get an environment variable from any available source.
          
          Searches all sources (cached after first load):
          1. OS environment variables
          2. Platform-specific sources (Claude.ai project, Claude Code settings, etc.)
          3. Standard .env files
          
          Args:
              key: Environment variable name
              default: Default value if not found
              required: If True, raise ValueError when not found
              validator: Optional function to validate the value
              
          Returns:
              The value, or default if not found
              
          Raises:
              ValueError: If required=True and key not found, or if validator fails
          
          Example:
              api_key = get_env("TURSO_TOKEN", required=True)
              port = get_env("PORT", default="8080")
          """
          env = load_all()
          value = env.get(key)
          
          if value is None:
              if required:
                  env_type = detect_environment()
                  raise ValueError(_get_missing_key_message(key, env_type))
              return default
          
          if validator and not validator(value):
              raise ValueError(f"Environment variable {key} failed validation")
          
          return value
      
      
      def load_env(path: str | Path) -> dict[str, str]:
          """
          Load environment variables from a specific file.
          
          Automatically detects file format:
          - .env, .local, no extension: KEY=value format
          - .json: JSON with "env" block
          - .toml: TOML with shell_environment_policy.set
          - .txt: Single value file
          
          Args:
              path: Path to the environment file
              
          Returns:
              Dict of loaded variables (also merged into cache)
          """
          global _cache
          
          path = Path(path)
          
          if path.suffix in ('.json',):
              loaded = _parse_json_settings(path)
          elif path.suffix in ('.toml',):
              loaded = _parse_toml_env(path)
          elif path.suffix in ('.txt',):
              key_name = path.stem.upper().replace("-", "_")
              loaded = _parse_single_value_file(path, key_name)
          else:
              loaded = _parse_env_file(path)
          
          _cache.update(loaded)
          return loaded
      
      
      def mask_secret(value: str, show_chars: int = 4) -> str:
          """
          Mask a secret value for safe logging/display.
          
          Args:
              value: The secret value
              show_chars: Number of characters to show at start and end
              
          Returns:
              Masked value like "sk-a...xyz"
          
          Example:
              >>> mask_secret("sk-ant-api03-abcdef123456")
              'sk-a...3456'
          """
          if len(value) <= show_chars * 2:
              return "***"
          return f"{value[:show_chars]}...{value[-show_chars:]}"
      
      
      def get_loaded_sources() -> list[str]:
          """Return list of sources that were loaded."""
          return _loaded_sources.copy()
      
      
      def debug_info() -> dict[str, Any]:
          """
          Get debug information about the current environment.
          
          Returns:
              Dict with environment type, loaded sources, and masked key previews
          """
          env = load_all()
          
          # Only show keys that look like credentials
          credential_patterns = ['KEY', 'TOKEN', 'SECRET', 'PASSWORD', 'CREDENTIAL', 'AUTH']
          masked_creds = {}
          
          for key, value in env.items():
              if any(pattern in key.upper() for pattern in credential_patterns):
                  masked_creds[key] = mask_secret(value)
          
          return {
              "environment": detect_environment(),
              "sources": get_loaded_sources(),
              "credential_keys": list(masked_creds.keys()),
              "credentials_masked": masked_creds,
          }
      
      
      # =============================================================================
      # Error Messages
      # =============================================================================
      
      def _get_missing_key_message(key: str, env_type: str) -> str:
          """Generate a helpful error message for a missing environment variable."""
          
          base_msg = f"Required environment variable '{key}' not found."
          
          hints = {
              "claude.ai": f"""
      {base_msg}
      
      In Claude.ai Projects, set this by:
      1. Go to Project Settings > Knowledge
      2. Upload a file named '{key.lower()}.txt' containing just the value, OR
      3. Upload a '{key.lower()}.env' file with {key}=your_value_here
      """,
              "claude-code-desktop": f"""
      {base_msg}
      
      In Claude Code, set this by:
      1. Add to ~/.claude/settings.json:
         {{"env": {{"{key}": "your_value_here"}}}}
         
      2. Or set as environment variable:
         export {key}="your_value_here"
      """,
              "claude-code-web": f"""
      {base_msg}
      
      Set as environment variable:
         export {key}="your_value_here"
      """,
              "codex": f"""
      {base_msg}
      
      In OpenAI Codex, set this by:
      1. Add to setup script (writes to ~/.bashrc):
         echo 'export {key}="your_value_here"' >> ~/.bashrc
      
      2. Or in ~/.codex/config.toml:
         [shell_environment_policy]
         set = {{ {key} = "your_value_here" }}
         
      Note: Secrets are only available during setup phase, not agent phase.
      """,
              "jules": f"""
      {base_msg}
      
      In Jules (Google), set this by:
      1. Go to Environment Settings in your Jules project
      2. Add {key} as an environment variable or secret
         
      Alternatively, add to .env file in your repo root:
         {key}=your_value_here
      """,
              "unknown": f"""
      {base_msg}
      
      Set as environment variable:
         export {key}="your_value_here"
         
      Or add to .env file in working directory:
         {key}=your_value_here
      """
          }
          
          return hints.get(env_type, hints["unknown"])
      
      
      # =============================================================================
      # CLI Interface
      # =============================================================================
      
      if __name__ == "__main__":
          import sys
          
          if len(sys.argv) > 1:
              # Get specific key
              key = sys.argv[1]
              value = get_env(key)
              if value:
                  print(value)
              else:
                  print(f"Not found: {key}", file=sys.stderr)
                  sys.exit(1)
          else:
              # Print debug info
              info = debug_info()
              print(f"Environment: {info['environment']}")
              print(f"Sources: {', '.join(info['sources'])}")
              print(f"Credential keys found: {', '.join(info['credential_keys']) or 'none'}")
              if info['credentials_masked']:
                  print("\nMasked values:")
                  for k, v in info['credentials_masked'].items():
                      print(f"  {k}: {v}")
      
    • __init__.py 0 B
  • .gitignore 202 B · in bundle
  • CHANGELOG.md 920 B
    # configuring - Changelog
    
    All notable changes to the `configuring` skill are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
    
    ## [2.0.0] - 2026-01-22
    
    ### Added
    
    - rename getting-env to configuring for Python import compatibility
    
    ## [2.0.0] - 2026-01-22
    
    ### Changed
    
    - **BREAKING**: Renamed skill from `getting-env` to `configuring` for Python import compatibility
    - Updated all import examples to use `from configuring import ...`
    - Expanded scope to cover environment variables, secrets, and other opinionated configuration setups
    
    ### Migration
    
    Replace imports:
    ```python
    # Old
    from getting_env import get_env
    
    # New
    import sys
    sys.path.insert(0, '/path/to/claude-skills')
    from configuring import get_env
    ```
    
    ## [1.0.1] - 2026-01-22
    
    ### Added
    
    - extend env loading for codex and turso
    
    ## [1.0.0] - 2026-01-22
    
    ### Added
    
    - Add/Update skill: getting-env
  • README.md 201 B
    # configuring
    
    Universal configuration management for AI agent environments. Loads environment variables, secrets, and config from Claude.ai, Claude Code, OpenAI Codex, Jules, and standard .env files.
    
  • SKILL.md 3.3 KB
    ---
    name: configuring
    description: Universal environment variable loader for AI agent environments. Loads secrets and config from Claude.ai, Claude Code, OpenAI Codex, Jules, and standard .env files.
    metadata:
      version: 2.0.0
      replaces: api-credentials, getting-env
    ---
    
    # Configuring
    
    Unified configuration management across AI coding environments. Load environment variables, secrets, and other opinionated configuration setups from any AI coding platform.
    
    ## Quick Start
    
    ```python
    import sys
    sys.path.insert(0, '/path/to/claude-skills')  # or wherever skills are installed
    from configuring import get_env, detect_environment
    
    # Get a variable (searches all sources automatically)
    token = get_env("TURSO_TOKEN", required=True)
    
    # With default
    port = get_env("PORT", default="8080")
    
    # What environment are we in?
    env = detect_environment()  # "claude.ai", "claude-code-desktop", "codex", "jules", etc.
    ```
    
    ## Supported Environments
    
    | Environment | Config Sources |
    |-------------|----------------|
    | **Claude.ai Projects** | `/mnt/project/*.env`, `/mnt/project/*-token.txt` |
    | **Claude Code** | `~/.claude/settings.json` (`env` block), `.claude/settings.json` |
    | **OpenAI Codex** | `~/.codex/config.toml`, setup script → `~/.bashrc`, `shell_snapshots/*.sh` |
    | **Jules** | Environment settings UI, `.env` in repo |
    | **Universal** | `os.environ`, `.env`, `.env.local` |
    
    ## API Reference
    
    ```python
    # Core
    get_env(key, default=None, *, required=False, validator=None) -> str | None
    load_env(path) -> dict[str, str]           # Load specific file
    load_all(force_reload=False) -> dict       # Load all sources
    
    # Utilities
    detect_environment() -> str                 # Current platform
    mask_secret(value, show_chars=4) -> str    # Safe logging
    debug_info() -> dict                        # Troubleshooting
    get_loaded_sources() -> list[str]          # What was checked
    ```
    
    ## Credential File Formats
    
    **`.env` files** (KEY=value):
    ```
    TURSO_TOKEN=eyJhbGciOiJFZERTQSI...
    EMBEDDING_API_KEY=sk-svcacct-...
    ```
    
    **Single-value files** (`*-token.txt`, `*-key.txt`):
    ```
    eyJhbGciOiJFZERTQSI...
    ```
    Filename becomes key: `turso-token.txt` → `TURSO_TOKEN`
    
    **Claude Code settings.json**:
    ```json
    {
      "env": {
        "TURSO_TOKEN": "eyJhbGciOiJFZERTQSI..."
      }
    }
    ```
    
    ## Priority Order
    
    Later sources override earlier:
    1. OS environment variables
    2. Platform-specific sources (detected automatically)
    3. `.env` files in cwd
    4. OS environment variables (again - explicit exports always win)
    
    ## Debugging
    
    ```python
    import sys
    sys.path.insert(0, '/path/to/claude-skills')
    from configuring import debug_info
    print(debug_info())
    # {'environment': 'claude.ai', 'sources': ['os.environ', 'claude.ai:/mnt/project/'], ...}
    ```
    
    CLI:
    ```bash
    cd /path/to/claude-skills/configuring
    python scripts/getting_env.py                    # Show debug info
    python scripts/getting_env.py TURSO_TOKEN        # Get specific key
    ```
    
    ## Migration from api-credentials / getting-env
    
    Replace:
    ```python
    # Old (api-credentials)
    from credentials import get_anthropic_api_key
    key = get_anthropic_api_key()
    
    # Old (getting-env)
    from getting_env import get_env
    key = get_env("ANTHROPIC_API_KEY")
    
    # New (configuring)
    import sys
    sys.path.insert(0, '/path/to/claude-skills')
    from configuring import get_env
    key = get_env("ANTHROPIC_API_KEY", required=True)
    ```
    
  • __init__.py 681 B
    """
    configuring: Universal configuration management for AI agent environments.
    
    Usage:
        import sys
        sys.path.insert(0, '/path/to/claude-skills')
        from configuring import get_env, detect_environment
    
        token = get_env("MY_TOKEN", required=True)
        env = detect_environment()  # "claude.ai", "claude-code-desktop", etc.
    """
    
    from .scripts.getting_env import (
        __version__,
        debug_info,
        detect_environment,
        get_env,
        get_loaded_sources,
        load_all,
        load_env,
        mask_secret,
    )
    
    __all__ = [
        "__version__",
        "debug_info",
        "detect_environment",
        "get_env",
        "get_loaded_sources",
        "load_all",
        "load_env",
        "mask_secret",
    ]
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related