Update skill versions to 1.4 and 1.5 across multiple skills, including infographics, latex-posters, literature-review, scientific-schematics, and scientific-slides. Enhance API key resolution logic in various scripts to improve credential handling and error messaging. Adjust image model references for clarity in infographic and schematic generation scripts.
This commit is contained in:
@@ -548,7 +548,7 @@ This repository contains **158 scientific and research skills** organized across
|
||||
- Diagrams: Scientific Schematics, Markdown & Mermaid Writing
|
||||
- Infographics: Infographics (10 types, 8 styles, colorblind-safe palettes)
|
||||
- Citations: Citation Management, pyzotero
|
||||
- Illustration: Generate Image (AI image generation with FLUX.2 Pro and Gemini 3.1 Flash Image Preview / Nano Banana 2)
|
||||
- Illustration: Generate Image (AI image generation with FLUX.2 Pro and Gemini 3.1 Flash Image / Nano Banana 2)
|
||||
|
||||
#### 🔬 **Scientific Databases & Data Access** (10 skills → 100+ databases total)
|
||||
> A unified database-lookup skill provides deterministic REST API access to 78 public databases across all domains, with retrieval contracts, pagination/count reconciliation, and endpoint provenance. Dedicated skills cover specialized data platforms. Multi-database packages like BioServices (~40 bioinformatics services), BioPython (39 NCBI sub-databases via Entrez), and gget (20+ genomics databases) add further coverage.
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Comprehensive citation management for academic research. Search Goo
|
||||
allowed-tools: Read Write Edit Bash
|
||||
license: MIT License
|
||||
metadata:
|
||||
version: "1.5"
|
||||
version: "1.6"
|
||||
skill-author: K-Dense Inc.
|
||||
openclaw:
|
||||
primaryEnv: OPENROUTER_API_KEY
|
||||
|
||||
@@ -40,6 +40,43 @@ FORWARDED_ENV_VARS = (
|
||||
)
|
||||
|
||||
|
||||
def resolve_api_key(explicit=None):
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. The child process is handed the resolved value through
|
||||
build_subprocess_env, so it never has to repeat this search.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def build_subprocess_env(api_key):
|
||||
"""Return a minimal environment for the AI generation subprocess."""
|
||||
env = {name: os.environ[name] for name in FORWARDED_ENV_VARS if name in os.environ}
|
||||
@@ -111,15 +148,16 @@ Environment Variables:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nFor AI generation, you need an OpenRouter API key.")
|
||||
print("Get one at: https://openrouter.ai/keys")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr use --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or use --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Find AI generation script
|
||||
|
||||
@@ -33,20 +33,42 @@ except ImportError:
|
||||
print("Error: requests library not found. Install with: pip install requests")
|
||||
sys.exit(1)
|
||||
|
||||
# Try to load .env file from multiple potential locations
|
||||
def _load_env_file():
|
||||
"""Load .env file from current directory or script directory only."""
|
||||
def _resolve_api_key(explicit: Optional[str] = None) -> Optional[str]:
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. Only the standard library is used: python-dotenv is a
|
||||
common omission, and a missing optional dependency should not read as a
|
||||
missing credential.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
return False
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
for candidate in [Path.cwd() / ".env", Path(__file__).resolve().parent / ".env"]:
|
||||
if candidate.exists():
|
||||
load_dotenv(dotenv_path=candidate, override=False)
|
||||
return True
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
class ScientificSchematicGenerator:
|
||||
@@ -125,12 +147,7 @@ IMPORTANT - NO FIGURE NUMBERS:
|
||||
verbose: Print detailed progress information
|
||||
"""
|
||||
# Priority: 1) explicit api_key param, 2) environment variable, 3) .env file
|
||||
self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
|
||||
# If not found in environment, try loading from .env file
|
||||
if not self.api_key:
|
||||
_load_env_file()
|
||||
self.api_key = os.getenv("OPENROUTER_API_KEY")
|
||||
self.api_key = _resolve_api_key(api_key)
|
||||
|
||||
if not self.api_key:
|
||||
raise ValueError(
|
||||
@@ -144,9 +161,11 @@ IMPORTANT - NO FIGURE NUMBERS:
|
||||
self.verbose = verbose
|
||||
self._last_error = None # Track last error for better reporting
|
||||
self.base_url = "https://openrouter.ai/api/v1"
|
||||
# Nano Banana 2 - Google's advanced image generation model
|
||||
# https://openrouter.ai/google/gemini-3.6-flash
|
||||
self.image_model = "google/gemini-3.1-flash-image-preview"
|
||||
# Nano Banana 2 - Google's advanced image generation model. The slug must
|
||||
# be an image-output model; a text-only chat model is rejected with
|
||||
# "No endpoints found that support the requested output modalities".
|
||||
# https://openrouter.ai/google/gemini-3.1-flash-image
|
||||
self.image_model = "google/gemini-3.1-flash-image"
|
||||
# Gemini 3.6 Flash for quality review - excellent vision and reasoning
|
||||
self.review_model = "google/gemini-3.6-flash"
|
||||
|
||||
@@ -776,13 +795,14 @@ Environment:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = _resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr provide via --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or provide via --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate iterations - enforce max of 2
|
||||
|
||||
@@ -3,7 +3,7 @@ name: infographics
|
||||
description: "Create professional infographics using Nano Banana Pro AI with smart iterative refinement. Uses Gemini 3.6 Flash for quality review. Integrates research-lookup and web search for accurate data. Supports 10 infographic types, 8 industry styles, and colorblind-safe palettes."
|
||||
allowed-tools: Read Write Edit Bash
|
||||
metadata:
|
||||
version: "1.3"
|
||||
version: "1.4"
|
||||
openclaw:
|
||||
primaryEnv: OPENROUTER_API_KEY
|
||||
envVars:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Generate professional infographics using Nano Banana Pro.
|
||||
|
||||
This script generates infographics with smart iterative refinement:
|
||||
- Uses Nano Banana Pro (Gemini 3.6 Flash Image Preview) for generation
|
||||
- Uses Nano Banana Pro (Gemini 3.1 Flash Image) for generation
|
||||
- Uses Gemini 3.6 Flash for quality review
|
||||
- Only regenerates if quality is below threshold
|
||||
- Supports 10 infographic types and industry style presets
|
||||
@@ -53,6 +53,43 @@ FORWARDED_ENV_VARS = (
|
||||
)
|
||||
|
||||
|
||||
def resolve_api_key(explicit=None):
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. The child process is handed the resolved value through
|
||||
build_subprocess_env, so it never has to repeat this search.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def build_subprocess_env(api_key):
|
||||
"""Return a minimal environment for the AI generation subprocess."""
|
||||
env = {name: os.environ[name] for name in FORWARDED_ENV_VARS if name in os.environ}
|
||||
@@ -194,15 +231,16 @@ Environment Variables:
|
||||
if not args.output:
|
||||
parser.error("--output is required")
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nFor AI generation, you need an OpenRouter API key.")
|
||||
print("Get one at: https://openrouter.ai/keys")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr use --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or use --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Find AI generation script
|
||||
|
||||
@@ -36,19 +36,42 @@ except ImportError:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _load_env_file():
|
||||
"""Load .env file from current directory or script directory only."""
|
||||
def _resolve_api_key(explicit: Optional[str] = None) -> Optional[str]:
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. Only the standard library is used: python-dotenv is a
|
||||
common omission, and a missing optional dependency should not read as a
|
||||
missing credential.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
return False
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
for candidate in [Path.cwd() / ".env", Path(__file__).resolve().parent / ".env"]:
|
||||
if candidate.exists():
|
||||
load_dotenv(dotenv_path=candidate, override=False)
|
||||
return True
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
# Infographic type configurations with detailed prompting
|
||||
@@ -315,11 +338,7 @@ IMPORTANT - NO META CONTENT:
|
||||
|
||||
def __init__(self, api_key: Optional[str] = None, verbose: bool = False):
|
||||
"""Initialize the generator."""
|
||||
self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
|
||||
if not self.api_key:
|
||||
_load_env_file()
|
||||
self.api_key = os.getenv("OPENROUTER_API_KEY")
|
||||
self.api_key = _resolve_api_key(api_key)
|
||||
|
||||
if not self.api_key:
|
||||
raise ValueError(
|
||||
@@ -333,9 +352,12 @@ IMPORTANT - NO META CONTENT:
|
||||
self.verbose = verbose
|
||||
self._last_error = None
|
||||
self.base_url = "https://openrouter.ai/api/v1"
|
||||
# Nano Banana Pro for image generation
|
||||
self.image_model = "google/gemini-3.6-flash"
|
||||
# Gemini 3.6 Flash for quality review
|
||||
# Nano Banana Pro for image generation. The slug must be an image-output
|
||||
# model; a text-only chat model is rejected with "No endpoints found that
|
||||
# support the requested output modalities".
|
||||
# https://openrouter.ai/google/gemini-3.1-flash-image
|
||||
self.image_model = "google/gemini-3.1-flash-image"
|
||||
# Gemini 3.6 Flash for quality review - reads the image, answers in text
|
||||
self.review_model = "google/gemini-3.6-flash"
|
||||
|
||||
def _log(self, message: str):
|
||||
@@ -1278,13 +1300,14 @@ Environment:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = _resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr provide via --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or provide via --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
|
||||
@@ -3,7 +3,7 @@ name: latex-posters
|
||||
description: "Create professional research posters in LaTeX using beamerposter, tikzposter, or baposter. Support for conference presentations, academic posters, and scientific communication. Includes layout design, color schemes, multi-column formats, figure integration, and poster-specific best practices for visual communication."
|
||||
allowed-tools: Read Write Edit Bash
|
||||
metadata:
|
||||
version: "1.3"
|
||||
version: "1.4"
|
||||
openclaw:
|
||||
primaryEnv: OPENROUTER_API_KEY
|
||||
envVars:
|
||||
|
||||
@@ -40,6 +40,43 @@ FORWARDED_ENV_VARS = (
|
||||
)
|
||||
|
||||
|
||||
def resolve_api_key(explicit=None):
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. The child process is handed the resolved value through
|
||||
build_subprocess_env, so it never has to repeat this search.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def build_subprocess_env(api_key):
|
||||
"""Return a minimal environment for the AI generation subprocess."""
|
||||
env = {name: os.environ[name] for name in FORWARDED_ENV_VARS if name in os.environ}
|
||||
@@ -111,15 +148,16 @@ Environment Variables:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nFor AI generation, you need an OpenRouter API key.")
|
||||
print("Get one at: https://openrouter.ai/keys")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr use --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or use --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Find AI generation script
|
||||
|
||||
@@ -33,20 +33,42 @@ except ImportError:
|
||||
print("Error: requests library not found. Install with: pip install requests")
|
||||
sys.exit(1)
|
||||
|
||||
# Try to load .env file from multiple potential locations
|
||||
def _load_env_file():
|
||||
"""Load .env file from current directory or script directory only."""
|
||||
def _resolve_api_key(explicit: Optional[str] = None) -> Optional[str]:
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. Only the standard library is used: python-dotenv is a
|
||||
common omission, and a missing optional dependency should not read as a
|
||||
missing credential.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
return False
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
for candidate in [Path.cwd() / ".env", Path(__file__).resolve().parent / ".env"]:
|
||||
if candidate.exists():
|
||||
load_dotenv(dotenv_path=candidate, override=False)
|
||||
return True
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
class ScientificSchematicGenerator:
|
||||
@@ -125,12 +147,7 @@ IMPORTANT - NO FIGURE NUMBERS:
|
||||
verbose: Print detailed progress information
|
||||
"""
|
||||
# Priority: 1) explicit api_key param, 2) environment variable, 3) .env file
|
||||
self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
|
||||
# If not found in environment, try loading from .env file
|
||||
if not self.api_key:
|
||||
_load_env_file()
|
||||
self.api_key = os.getenv("OPENROUTER_API_KEY")
|
||||
self.api_key = _resolve_api_key(api_key)
|
||||
|
||||
if not self.api_key:
|
||||
raise ValueError(
|
||||
@@ -144,9 +161,11 @@ IMPORTANT - NO FIGURE NUMBERS:
|
||||
self.verbose = verbose
|
||||
self._last_error = None # Track last error for better reporting
|
||||
self.base_url = "https://openrouter.ai/api/v1"
|
||||
# Nano Banana 2 - Google's advanced image generation model
|
||||
# https://openrouter.ai/google/gemini-3.6-flash
|
||||
self.image_model = "google/gemini-3.1-flash-image-preview"
|
||||
# Nano Banana 2 - Google's advanced image generation model. The slug must
|
||||
# be an image-output model; a text-only chat model is rejected with
|
||||
# "No endpoints found that support the requested output modalities".
|
||||
# https://openrouter.ai/google/gemini-3.1-flash-image
|
||||
self.image_model = "google/gemini-3.1-flash-image"
|
||||
# Gemini 3.6 Flash for quality review - excellent vision and reasoning
|
||||
self.review_model = "google/gemini-3.6-flash"
|
||||
|
||||
@@ -776,13 +795,14 @@ Environment:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = _resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr provide via --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or provide via --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate iterations - enforce max of 2
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Conduct comprehensive, systematic literature reviews using multiple
|
||||
allowed-tools: Read Write Edit Bash
|
||||
license: MIT license
|
||||
metadata:
|
||||
version: "1.4"
|
||||
version: "1.5"
|
||||
skill-author: K-Dense Inc.
|
||||
openclaw:
|
||||
primaryEnv: OPENROUTER_API_KEY
|
||||
|
||||
@@ -40,6 +40,43 @@ FORWARDED_ENV_VARS = (
|
||||
)
|
||||
|
||||
|
||||
def resolve_api_key(explicit=None):
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. The child process is handed the resolved value through
|
||||
build_subprocess_env, so it never has to repeat this search.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def build_subprocess_env(api_key):
|
||||
"""Return a minimal environment for the AI generation subprocess."""
|
||||
env = {name: os.environ[name] for name in FORWARDED_ENV_VARS if name in os.environ}
|
||||
@@ -111,15 +148,16 @@ Environment Variables:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nFor AI generation, you need an OpenRouter API key.")
|
||||
print("Get one at: https://openrouter.ai/keys")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr use --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or use --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Find AI generation script
|
||||
|
||||
@@ -33,20 +33,42 @@ except ImportError:
|
||||
print("Error: requests library not found. Install with: pip install requests")
|
||||
sys.exit(1)
|
||||
|
||||
# Try to load .env file from multiple potential locations
|
||||
def _load_env_file():
|
||||
"""Load .env file from current directory or script directory only."""
|
||||
def _resolve_api_key(explicit: Optional[str] = None) -> Optional[str]:
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. Only the standard library is used: python-dotenv is a
|
||||
common omission, and a missing optional dependency should not read as a
|
||||
missing credential.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
return False
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
for candidate in [Path.cwd() / ".env", Path(__file__).resolve().parent / ".env"]:
|
||||
if candidate.exists():
|
||||
load_dotenv(dotenv_path=candidate, override=False)
|
||||
return True
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
class ScientificSchematicGenerator:
|
||||
@@ -125,12 +147,7 @@ IMPORTANT - NO FIGURE NUMBERS:
|
||||
verbose: Print detailed progress information
|
||||
"""
|
||||
# Priority: 1) explicit api_key param, 2) environment variable, 3) .env file
|
||||
self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
|
||||
# If not found in environment, try loading from .env file
|
||||
if not self.api_key:
|
||||
_load_env_file()
|
||||
self.api_key = os.getenv("OPENROUTER_API_KEY")
|
||||
self.api_key = _resolve_api_key(api_key)
|
||||
|
||||
if not self.api_key:
|
||||
raise ValueError(
|
||||
@@ -144,9 +161,11 @@ IMPORTANT - NO FIGURE NUMBERS:
|
||||
self.verbose = verbose
|
||||
self._last_error = None # Track last error for better reporting
|
||||
self.base_url = "https://openrouter.ai/api/v1"
|
||||
# Nano Banana 2 - Google's advanced image generation model
|
||||
# https://openrouter.ai/google/gemini-3.6-flash
|
||||
self.image_model = "google/gemini-3.1-flash-image-preview"
|
||||
# Nano Banana 2 - Google's advanced image generation model. The slug must
|
||||
# be an image-output model; a text-only chat model is rejected with
|
||||
# "No endpoints found that support the requested output modalities".
|
||||
# https://openrouter.ai/google/gemini-3.1-flash-image
|
||||
self.image_model = "google/gemini-3.1-flash-image"
|
||||
# Gemini 3.6 Flash for quality review - excellent vision and reasoning
|
||||
self.review_model = "google/gemini-3.6-flash"
|
||||
|
||||
@@ -776,13 +795,14 @@ Environment:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = _resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr provide via --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or provide via --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate iterations - enforce max of 2
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Create publication-quality scientific diagrams using Nano Banana 2
|
||||
allowed-tools: Read Write Edit Bash
|
||||
license: MIT license
|
||||
metadata:
|
||||
version: "1.3"
|
||||
version: "1.4"
|
||||
skill-author: K-Dense Inc.
|
||||
openclaw:
|
||||
primaryEnv: OPENROUTER_API_KEY
|
||||
|
||||
@@ -40,6 +40,43 @@ FORWARDED_ENV_VARS = (
|
||||
)
|
||||
|
||||
|
||||
def resolve_api_key(explicit=None):
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. The child process is handed the resolved value through
|
||||
build_subprocess_env, so it never has to repeat this search.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def build_subprocess_env(api_key):
|
||||
"""Return a minimal environment for the AI generation subprocess."""
|
||||
env = {name: os.environ[name] for name in FORWARDED_ENV_VARS if name in os.environ}
|
||||
@@ -111,15 +148,16 @@ Environment Variables:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nFor AI generation, you need an OpenRouter API key.")
|
||||
print("Get one at: https://openrouter.ai/keys")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr use --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or use --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Find AI generation script
|
||||
|
||||
@@ -33,20 +33,42 @@ except ImportError:
|
||||
print("Error: requests library not found. Install with: pip install requests")
|
||||
sys.exit(1)
|
||||
|
||||
# Try to load .env file from multiple potential locations
|
||||
def _load_env_file():
|
||||
"""Load .env file from current directory or script directory only."""
|
||||
def _resolve_api_key(explicit: Optional[str] = None) -> Optional[str]:
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. Only the standard library is used: python-dotenv is a
|
||||
common omission, and a missing optional dependency should not read as a
|
||||
missing credential.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
return False
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
for candidate in [Path.cwd() / ".env", Path(__file__).resolve().parent / ".env"]:
|
||||
if candidate.exists():
|
||||
load_dotenv(dotenv_path=candidate, override=False)
|
||||
return True
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
class ScientificSchematicGenerator:
|
||||
@@ -125,12 +147,7 @@ IMPORTANT - NO FIGURE NUMBERS:
|
||||
verbose: Print detailed progress information
|
||||
"""
|
||||
# Priority: 1) explicit api_key param, 2) environment variable, 3) .env file
|
||||
self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
|
||||
# If not found in environment, try loading from .env file
|
||||
if not self.api_key:
|
||||
_load_env_file()
|
||||
self.api_key = os.getenv("OPENROUTER_API_KEY")
|
||||
self.api_key = _resolve_api_key(api_key)
|
||||
|
||||
if not self.api_key:
|
||||
raise ValueError(
|
||||
@@ -144,9 +161,11 @@ IMPORTANT - NO FIGURE NUMBERS:
|
||||
self.verbose = verbose
|
||||
self._last_error = None # Track last error for better reporting
|
||||
self.base_url = "https://openrouter.ai/api/v1"
|
||||
# Nano Banana 2 - Google's advanced image generation model
|
||||
# https://openrouter.ai/google/gemini-3.6-flash
|
||||
self.image_model = "google/gemini-3.1-flash-image-preview"
|
||||
# Nano Banana 2 - Google's advanced image generation model. The slug must
|
||||
# be an image-output model; a text-only chat model is rejected with
|
||||
# "No endpoints found that support the requested output modalities".
|
||||
# https://openrouter.ai/google/gemini-3.1-flash-image
|
||||
self.image_model = "google/gemini-3.1-flash-image"
|
||||
# Gemini 3.6 Flash for quality review - excellent vision and reasoning
|
||||
self.review_model = "google/gemini-3.6-flash"
|
||||
|
||||
@@ -776,13 +795,14 @@ Environment:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = _resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr provide via --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or provide via --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate iterations - enforce max of 2
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Build slide decks and presentations for research talks. Use this fo
|
||||
allowed-tools: Read Write Edit Bash
|
||||
license: MIT license
|
||||
metadata:
|
||||
version: "1.4"
|
||||
version: "1.5"
|
||||
skill-author: K-Dense Inc.
|
||||
openclaw:
|
||||
primaryEnv: OPENROUTER_API_KEY
|
||||
|
||||
@@ -40,6 +40,43 @@ FORWARDED_ENV_VARS = (
|
||||
)
|
||||
|
||||
|
||||
def resolve_api_key(explicit=None):
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. The child process is handed the resolved value through
|
||||
build_subprocess_env, so it never has to repeat this search.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def build_subprocess_env(api_key):
|
||||
"""Return a minimal environment for the AI generation subprocess."""
|
||||
env = {name: os.environ[name] for name in FORWARDED_ENV_VARS if name in os.environ}
|
||||
@@ -111,15 +148,16 @@ Environment Variables:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nFor AI generation, you need an OpenRouter API key.")
|
||||
print("Get one at: https://openrouter.ai/keys")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr use --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or use --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Find AI generation script
|
||||
|
||||
@@ -33,20 +33,42 @@ except ImportError:
|
||||
print("Error: requests library not found. Install with: pip install requests")
|
||||
sys.exit(1)
|
||||
|
||||
# Try to load .env file from multiple potential locations
|
||||
def _load_env_file():
|
||||
"""Load .env file from current directory or script directory only."""
|
||||
def _resolve_api_key(explicit: Optional[str] = None) -> Optional[str]:
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. Only the standard library is used: python-dotenv is a
|
||||
common omission, and a missing optional dependency should not read as a
|
||||
missing credential.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
return False
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
for candidate in [Path.cwd() / ".env", Path(__file__).resolve().parent / ".env"]:
|
||||
if candidate.exists():
|
||||
load_dotenv(dotenv_path=candidate, override=False)
|
||||
return True
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
class ScientificSchematicGenerator:
|
||||
@@ -125,12 +147,7 @@ IMPORTANT - NO FIGURE NUMBERS:
|
||||
verbose: Print detailed progress information
|
||||
"""
|
||||
# Priority: 1) explicit api_key param, 2) environment variable, 3) .env file
|
||||
self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
|
||||
# If not found in environment, try loading from .env file
|
||||
if not self.api_key:
|
||||
_load_env_file()
|
||||
self.api_key = os.getenv("OPENROUTER_API_KEY")
|
||||
self.api_key = _resolve_api_key(api_key)
|
||||
|
||||
if not self.api_key:
|
||||
raise ValueError(
|
||||
@@ -144,9 +161,11 @@ IMPORTANT - NO FIGURE NUMBERS:
|
||||
self.verbose = verbose
|
||||
self._last_error = None # Track last error for better reporting
|
||||
self.base_url = "https://openrouter.ai/api/v1"
|
||||
# Nano Banana 2 - Google's advanced image generation model
|
||||
# https://openrouter.ai/google/gemini-3.6-flash
|
||||
self.image_model = "google/gemini-3.1-flash-image-preview"
|
||||
# Nano Banana 2 - Google's advanced image generation model. The slug must
|
||||
# be an image-output model; a text-only chat model is rejected with
|
||||
# "No endpoints found that support the requested output modalities".
|
||||
# https://openrouter.ai/google/gemini-3.1-flash-image
|
||||
self.image_model = "google/gemini-3.1-flash-image"
|
||||
# Gemini 3.6 Flash for quality review - excellent vision and reasoning
|
||||
self.review_model = "google/gemini-3.6-flash"
|
||||
|
||||
@@ -776,13 +795,14 @@ Environment:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = _resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr provide via --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or provide via --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate iterations - enforce max of 2
|
||||
|
||||
@@ -43,6 +43,43 @@ FORWARDED_ENV_VARS = (
|
||||
)
|
||||
|
||||
|
||||
def resolve_api_key(explicit=None):
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. The child process is handed the resolved value through
|
||||
build_subprocess_env, so it never has to repeat this search.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def build_subprocess_env(api_key):
|
||||
"""Return a minimal environment for the AI generation subprocess."""
|
||||
env = {name: os.environ[name] for name in FORWARDED_ENV_VARS if name in os.environ}
|
||||
@@ -108,15 +145,16 @@ Environment Variables:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for API key
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Check for API key — resolves --api-key, the environment, then any .env file
|
||||
api_key = resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nFor AI generation, you need an OpenRouter API key.")
|
||||
print("Get one at: https://openrouter.ai/keys")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr use --api-key flag")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or use --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
# Find AI generation script
|
||||
|
||||
@@ -46,19 +46,42 @@ except ImportError:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _load_env_file():
|
||||
"""Load .env file from current directory or script directory only."""
|
||||
def _resolve_api_key(explicit: Optional[str] = None) -> Optional[str]:
|
||||
"""Resolve the OpenRouter key from --api-key, the environment, then any .env file.
|
||||
|
||||
The .env scan walks up from the working directory and finally checks the
|
||||
script's own directory, so running from anywhere inside a project picks up
|
||||
the key at its root. Only the standard library is used: python-dotenv is a
|
||||
common omission, and a missing optional dependency should not read as a
|
||||
missing credential.
|
||||
"""
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
cwd = Path.cwd()
|
||||
for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
|
||||
env_file = directory / ".env"
|
||||
if not env_file.is_file():
|
||||
continue
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
return False
|
||||
content = env_file.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
for raw in content.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
name, _, value = line.partition("=")
|
||||
if name.strip() == "OPENROUTER_API_KEY":
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
return value
|
||||
|
||||
for candidate in [Path.cwd() / ".env", Path(__file__).resolve().parent / ".env"]:
|
||||
if candidate.exists():
|
||||
load_dotenv(dotenv_path=candidate, override=False)
|
||||
return True
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
class SlideImageGenerator:
|
||||
@@ -152,11 +175,7 @@ STYLE:
|
||||
api_key: OpenRouter API key (or use OPENROUTER_API_KEY env var)
|
||||
verbose: Print detailed progress information
|
||||
"""
|
||||
self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
|
||||
if not self.api_key:
|
||||
_load_env_file()
|
||||
self.api_key = os.getenv("OPENROUTER_API_KEY")
|
||||
self.api_key = _resolve_api_key(api_key)
|
||||
|
||||
if not self.api_key:
|
||||
raise ValueError(
|
||||
@@ -170,9 +189,12 @@ STYLE:
|
||||
self.verbose = verbose
|
||||
self._last_error = None
|
||||
self.base_url = "https://openrouter.ai/api/v1"
|
||||
# Nano Banana Pro for image generation
|
||||
self.image_model = "google/gemini-3.6-flash"
|
||||
# Gemini 3.6 Flash for quality review
|
||||
# Nano Banana Pro for image generation. The slug must be an image-output
|
||||
# model; a text-only chat model is rejected with "No endpoints found that
|
||||
# support the requested output modalities".
|
||||
# https://openrouter.ai/google/gemini-3.1-flash-image
|
||||
self.image_model = "google/gemini-3.1-flash-image"
|
||||
# Gemini 3.6 Flash for quality review - reads the image, answers in text
|
||||
self.review_model = "google/gemini-3.6-flash"
|
||||
|
||||
def _log(self, message: str):
|
||||
@@ -697,11 +719,14 @@ Environment:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
api_key = args.api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
# Resolves --api-key, the environment, then any .env file
|
||||
api_key = _resolve_api_key(args.api_key)
|
||||
if not api_key:
|
||||
print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
print("Error: OPENROUTER_API_KEY not found")
|
||||
print("\nSet it with:")
|
||||
print(" export OPENROUTER_API_KEY='your_api_key'")
|
||||
print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
|
||||
print("Or provide via --api-key flag")
|
||||
sys.exit(1)
|
||||
|
||||
if args.iterations < 1 or args.iterations > 2:
|
||||
|
||||
@@ -620,6 +620,13 @@ class SlideImageInvocationTests(TemporaryDirectoryTestCase):
|
||||
self.assertEqual(clamped[clamped.index("--iterations") + 1], "1")
|
||||
|
||||
def test_without_a_key_nothing_is_spawned(self) -> None:
|
||||
# The wrapper also resolves a credential from any .env file at or above the
|
||||
# working directory, so the temporary root stands in for a machine that has
|
||||
# none -- otherwise a developer's own .env would satisfy the lookup.
|
||||
origin = os.getcwd()
|
||||
self.addCleanup(os.chdir, origin)
|
||||
os.chdir(self.root)
|
||||
|
||||
argv = ["generate_slide_image.py", "a slide", "-o", "s.png"]
|
||||
with mock.patch.object(sys, "argv", argv), \
|
||||
mock.patch.dict(os.environ, {}, clear=True), \
|
||||
@@ -948,6 +955,10 @@ class AiCliValidationTests(TemporaryDirectoryTestCase):
|
||||
text=True,
|
||||
timeout=120,
|
||||
env=environment,
|
||||
# The script resolves a credential from any .env file at or above the
|
||||
# working directory. Running from the temporary root keeps a developer's
|
||||
# real .env out of reach, so key=None genuinely means "no credential".
|
||||
cwd=self.root,
|
||||
)
|
||||
|
||||
def test_more_iterations_than_allowed_is_refused(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user