feat: Add web-only fallback mode with API key promo
- Skill now works without any API keys using WebSearch fallback - Shows promo banner marketing Reddit/X data when keys are missing - Partial mode (one key) shows shorter tip for the missing source - Updated SKILL.md to document three modes: Full, Partial, Web-Only - Added get_missing_keys() to env.py for promo logic - Added show_promo(), start_web_only(), show_web_only_complete() to ui.py - Updated render_compact() to include inline promo for web-only mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -50,66 +50,64 @@ Common patterns:
|
|||||||
|
|
||||||
## Setup Check
|
## Setup Check
|
||||||
|
|
||||||
Verify API key configuration exists:
|
The skill works in three modes based on available API keys:
|
||||||
|
|
||||||
```bash
|
1. **Full Mode** (both keys): Reddit + X + WebSearch - best results with engagement metrics
|
||||||
if [ ! -f ~/.config/last30days/.env ]; then
|
2. **Partial Mode** (one key): Reddit-only or X-only + WebSearch
|
||||||
echo "SETUP_NEEDED"
|
3. **Web-Only Mode** (no keys): WebSearch only - still useful, but no engagement metrics
|
||||||
else
|
|
||||||
echo "CONFIGURED"
|
|
||||||
fi
|
|
||||||
```
|
|
||||||
|
|
||||||
### If SETUP_NEEDED
|
**API keys are OPTIONAL.** The skill will work without them using WebSearch fallback.
|
||||||
|
|
||||||
Run NUX flow to configure API keys. Use AskUserQuestion to collect:
|
### First-Time Setup (Optional but Recommended)
|
||||||
|
|
||||||
1. **OpenAI API Key** (optional but recommended for Reddit research)
|
If the user wants to add API keys for better results:
|
||||||
2. **xAI API Key** (optional but recommended for X research)
|
|
||||||
|
|
||||||
Then create the config:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/.config/last30days
|
mkdir -p ~/.config/last30days
|
||||||
cat > ~/.config/last30days/.env << 'ENVEOF'
|
cat > ~/.config/last30days/.env << 'ENVEOF'
|
||||||
# last30days API Configuration
|
# last30days API Configuration
|
||||||
# At least one key is required
|
# Both keys are optional - skill works with WebSearch fallback
|
||||||
|
|
||||||
|
# For Reddit research (uses OpenAI's web_search tool)
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=
|
||||||
|
|
||||||
|
# For X/Twitter research (uses xAI's x_search tool)
|
||||||
XAI_API_KEY=
|
XAI_API_KEY=
|
||||||
ENVEOF
|
ENVEOF
|
||||||
|
|
||||||
chmod 600 ~/.config/last30days/.env
|
chmod 600 ~/.config/last30days/.env
|
||||||
echo "Config created at ~/.config/last30days/.env"
|
echo "Config created at ~/.config/last30days/.env"
|
||||||
echo "Please edit it to add your API keys, then run the skill again."
|
echo "Edit to add your API keys for enhanced research."
|
||||||
```
|
```
|
||||||
|
|
||||||
**STOP HERE if setup was needed.**
|
**DO NOT stop if no keys are configured.** Proceed with web-only mode.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Research Execution
|
## Research Execution
|
||||||
|
|
||||||
**IMPORTANT: Run Reddit/X script IN BACKGROUND first, then WebSearch.** This way both run in parallel.
|
**IMPORTANT: The script handles API key detection automatically.** Run it and check the output to determine mode.
|
||||||
|
|
||||||
**Step 1: Display the research banner immediately:**
|
**Step 1: Run the research script**
|
||||||
```
|
|
||||||
🔍 Researching "{TOPIC}" across the last 30 days...
|
|
||||||
|
|
||||||
🚀 Deploying research agents in parallel...
|
|
||||||
├─ 🟠 Reddit Agent: Scanning subreddits for gold...
|
|
||||||
├─ 🔵 X Agent: Catching the latest takes...
|
|
||||||
├─ 🌐 Web Agent: Crawling blogs, docs & news...
|
|
||||||
└─ ⚖️ Judge Agent: Standing by to synthesize...
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: Start Reddit/X script in background**
|
|
||||||
```bash
|
```bash
|
||||||
python3 ~/.claude/skills/last30days/scripts/last30days.py "$ARGUMENTS" --emit=compact 2>&1
|
python3 ~/.claude/skills/last30days/scripts/last30days.py "$ARGUMENTS" --emit=compact 2>&1
|
||||||
```
|
```
|
||||||
Use `run_in_background: true` so it starts immediately and runs while we do WebSearch.
|
|
||||||
|
|
||||||
**Step 2: While script runs, do WebSearch**
|
The script will automatically:
|
||||||
|
- Detect available API keys
|
||||||
|
- Show a promo banner if keys are missing (this is intentional marketing)
|
||||||
|
- Run Reddit/X searches if keys exist
|
||||||
|
- Signal if WebSearch is needed
|
||||||
|
|
||||||
|
**Step 2: Check the output mode**
|
||||||
|
|
||||||
|
The script output will indicate the mode:
|
||||||
|
- **"Mode: both"** or **"Mode: reddit-only"** or **"Mode: x-only"**: Script found results, WebSearch is supplementary
|
||||||
|
- **"Mode: web-only"**: No API keys, Claude must do ALL research via WebSearch
|
||||||
|
|
||||||
|
**Step 3: Do WebSearch**
|
||||||
|
|
||||||
|
For **ALL modes**, do WebSearch to supplement (or provide all data in web-only mode).
|
||||||
|
|
||||||
Choose search queries based on QUERY_TYPE:
|
Choose search queries based on QUERY_TYPE:
|
||||||
|
|
||||||
@@ -240,6 +238,8 @@ KEY PATTERNS I'll use:
|
|||||||
```
|
```
|
||||||
|
|
||||||
**THEN - Stats (right before invitation):**
|
**THEN - Stats (right before invitation):**
|
||||||
|
|
||||||
|
For **full/partial mode** (has API keys):
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
✅ All agents reported back!
|
✅ All agents reported back!
|
||||||
@@ -249,6 +249,18 @@ KEY PATTERNS I'll use:
|
|||||||
└─ Top voices: r/{sub1}, r/{sub2} │ @{handle1}, @{handle2} │ {web_author} on {site}
|
└─ Top voices: r/{sub1}, r/{sub2} │ @{handle1}, @{handle2} │ {web_author} on {site}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For **web-only mode** (no API keys):
|
||||||
|
```
|
||||||
|
---
|
||||||
|
✅ Research complete!
|
||||||
|
├─ 🌐 Web: {n} pages │ {domains}
|
||||||
|
└─ Top sources: {author1} on {site1}, {author2} on {site2}
|
||||||
|
|
||||||
|
💡 Want engagement metrics? Add API keys to ~/.config/last30days/.env
|
||||||
|
- OPENAI_API_KEY → Reddit (real upvotes & comments)
|
||||||
|
- XAI_API_KEY → X/Twitter (real likes & reposts)
|
||||||
|
```
|
||||||
|
|
||||||
**LAST - Invitation:**
|
**LAST - Invitation:**
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
@@ -358,6 +370,7 @@ Only do new research if the user explicitly asks about a DIFFERENT topic.
|
|||||||
|
|
||||||
After delivering a prompt, end with:
|
After delivering a prompt, end with:
|
||||||
|
|
||||||
|
For **full/partial mode**:
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
📚 Expert in: {TOPIC} for {TARGET_TOOL}
|
📚 Expert in: {TOPIC} for {TARGET_TOOL}
|
||||||
@@ -365,3 +378,14 @@ After delivering a prompt, end with:
|
|||||||
|
|
||||||
Want another prompt? Just tell me what you're creating next.
|
Want another prompt? Just tell me what you're creating next.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For **web-only mode**:
|
||||||
|
```
|
||||||
|
---
|
||||||
|
📚 Expert in: {TOPIC} for {TARGET_TOOL}
|
||||||
|
📊 Based on: {n} web pages from {domains}
|
||||||
|
|
||||||
|
Want another prompt? Just tell me what you're creating next.
|
||||||
|
|
||||||
|
💡 Unlock Reddit & X data: Add API keys to ~/.config/last30days/.env
|
||||||
|
```
|
||||||
|
|||||||
+21
-3
@@ -79,9 +79,16 @@ def run_research(
|
|||||||
reddit_error = None
|
reddit_error = None
|
||||||
x_error = None
|
x_error = None
|
||||||
|
|
||||||
# Check if WebSearch is needed
|
# Check if WebSearch is needed (always needed in web-only mode)
|
||||||
web_needed = sources in ("all", "web", "reddit-web", "x-web")
|
web_needed = sources in ("all", "web", "reddit-web", "x-web")
|
||||||
|
|
||||||
|
# Web-only mode: no API calls needed, Claude handles everything
|
||||||
|
if sources == "web":
|
||||||
|
if progress:
|
||||||
|
progress.start_web_only()
|
||||||
|
progress.end_web_only()
|
||||||
|
return reddit_items, x_items, True, raw_openai, raw_xai, raw_reddit_enriched, reddit_error, x_error
|
||||||
|
|
||||||
# Reddit search via OpenAI
|
# Reddit search via OpenAI
|
||||||
if sources in ("both", "reddit", "all", "reddit-web"):
|
if sources in ("both", "reddit", "all", "reddit-web"):
|
||||||
if progress:
|
if progress:
|
||||||
@@ -263,9 +270,16 @@ def main():
|
|||||||
# Get date range
|
# Get date range
|
||||||
from_date, to_date = dates.get_date_range(30)
|
from_date, to_date = dates.get_date_range(30)
|
||||||
|
|
||||||
|
# Check what keys are missing for promo messaging
|
||||||
|
missing_keys = env.get_missing_keys(config)
|
||||||
|
|
||||||
# Initialize progress display
|
# Initialize progress display
|
||||||
progress = ui.ProgressDisplay(args.topic, show_banner=True)
|
progress = ui.ProgressDisplay(args.topic, show_banner=True)
|
||||||
|
|
||||||
|
# Show promo for missing keys BEFORE research
|
||||||
|
if missing_keys != 'none':
|
||||||
|
progress.show_promo(missing_keys)
|
||||||
|
|
||||||
# Select models
|
# Select models
|
||||||
if args.mock:
|
if args.mock:
|
||||||
# Use mock models
|
# Use mock models
|
||||||
@@ -361,10 +375,13 @@ def main():
|
|||||||
render.write_outputs(report, raw_openai, raw_xai, raw_reddit_enriched)
|
render.write_outputs(report, raw_openai, raw_xai, raw_reddit_enriched)
|
||||||
|
|
||||||
# Show completion
|
# Show completion
|
||||||
|
if sources == "web":
|
||||||
|
progress.show_web_only_complete()
|
||||||
|
else:
|
||||||
progress.show_complete(len(deduped_reddit), len(deduped_x))
|
progress.show_complete(len(deduped_reddit), len(deduped_x))
|
||||||
|
|
||||||
# Output result
|
# Output result
|
||||||
output_result(report, args.emit, web_needed, args.topic, from_date, to_date)
|
output_result(report, args.emit, web_needed, args.topic, from_date, to_date, missing_keys)
|
||||||
|
|
||||||
|
|
||||||
def output_result(
|
def output_result(
|
||||||
@@ -374,10 +391,11 @@ def output_result(
|
|||||||
topic: str = "",
|
topic: str = "",
|
||||||
from_date: str = "",
|
from_date: str = "",
|
||||||
to_date: str = "",
|
to_date: str = "",
|
||||||
|
missing_keys: str = "none",
|
||||||
):
|
):
|
||||||
"""Output the result based on emit mode."""
|
"""Output the result based on emit mode."""
|
||||||
if emit_mode == "compact":
|
if emit_mode == "compact":
|
||||||
print(render.render_compact(report))
|
print(render.render_compact(report, missing_keys=missing_keys))
|
||||||
elif emit_mode == "json":
|
elif emit_mode == "json":
|
||||||
print(json.dumps(report.to_dict(), indent=2))
|
print(json.dumps(report.to_dict(), indent=2))
|
||||||
elif emit_mode == "md":
|
elif emit_mode == "md":
|
||||||
|
|||||||
@@ -72,6 +72,24 @@ def get_available_sources(config: Dict[str, Any]) -> str:
|
|||||||
return 'web' # Fallback: WebSearch only (no API keys needed)
|
return 'web' # Fallback: WebSearch only (no API keys needed)
|
||||||
|
|
||||||
|
|
||||||
|
def get_missing_keys(config: Dict[str, Any]) -> str:
|
||||||
|
"""Determine which API keys are missing.
|
||||||
|
|
||||||
|
Returns: 'both', 'reddit', 'x', or 'none'
|
||||||
|
"""
|
||||||
|
has_openai = bool(config.get('OPENAI_API_KEY'))
|
||||||
|
has_xai = bool(config.get('XAI_API_KEY'))
|
||||||
|
|
||||||
|
if has_openai and has_xai:
|
||||||
|
return 'none'
|
||||||
|
elif has_openai:
|
||||||
|
return 'x' # Missing xAI key
|
||||||
|
elif has_xai:
|
||||||
|
return 'reddit' # Missing OpenAI key
|
||||||
|
else:
|
||||||
|
return 'both' # Missing both keys
|
||||||
|
|
||||||
|
|
||||||
def validate_sources(requested: str, available: str, include_web: bool = False) -> tuple[str, Optional[str]]:
|
def validate_sources(requested: str, available: str, include_web: bool = False) -> tuple[str, Optional[str]]:
|
||||||
"""Validate requested sources against available keys.
|
"""Validate requested sources against available keys.
|
||||||
|
|
||||||
|
|||||||
+19
-6
@@ -14,12 +14,13 @@ def ensure_output_dir():
|
|||||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
def render_compact(report: schema.Report, limit: int = 15) -> str:
|
def render_compact(report: schema.Report, limit: int = 15, missing_keys: str = "none") -> str:
|
||||||
"""Render compact output for Claude to synthesize.
|
"""Render compact output for Claude to synthesize.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
report: Report data
|
report: Report data
|
||||||
limit: Max items per source
|
limit: Max items per source
|
||||||
|
missing_keys: 'both', 'reddit', 'x', or 'none'
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Compact markdown string
|
Compact markdown string
|
||||||
@@ -30,6 +31,18 @@ def render_compact(report: schema.Report, limit: int = 15) -> str:
|
|||||||
lines.append(f"## Research Results: {report.topic}")
|
lines.append(f"## Research Results: {report.topic}")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
|
# Web-only mode banner (when no API keys)
|
||||||
|
if report.mode == "web-only":
|
||||||
|
lines.append("**🌐 WEB SEARCH MODE** - Claude will search blogs, docs & news")
|
||||||
|
lines.append("")
|
||||||
|
lines.append("---")
|
||||||
|
lines.append("**⚡ Want better results?** Add API keys to unlock Reddit & X data:")
|
||||||
|
lines.append("- `OPENAI_API_KEY` → Reddit threads with real upvotes & comments")
|
||||||
|
lines.append("- `XAI_API_KEY` → X posts with real likes & reposts")
|
||||||
|
lines.append("- Edit `~/.config/last30days/.env` to add keys")
|
||||||
|
lines.append("---")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
# Cache indicator
|
# Cache indicator
|
||||||
if report.from_cache:
|
if report.from_cache:
|
||||||
age_str = f"{report.cache_age_hours:.1f}h old" if report.cache_age_hours else "cached"
|
age_str = f"{report.cache_age_hours:.1f}h old" if report.cache_age_hours else "cached"
|
||||||
@@ -44,12 +57,12 @@ def render_compact(report: schema.Report, limit: int = 15) -> str:
|
|||||||
lines.append(f"**xAI Model:** {report.xai_model_used}")
|
lines.append(f"**xAI Model:** {report.xai_model_used}")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# Coverage note
|
# Coverage note for partial coverage
|
||||||
if report.mode == "reddit-only":
|
if report.mode == "reddit-only" and missing_keys == "x":
|
||||||
lines.append("*Tip: Add xAI key for X coverage and better triangulation.*")
|
lines.append("*💡 Tip: Add XAI_API_KEY for X/Twitter data and better triangulation.*")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
elif report.mode == "x-only":
|
elif report.mode == "x-only" and missing_keys == "reddit":
|
||||||
lines.append("*Tip: Add OpenAI key for Reddit coverage and better triangulation.*")
|
lines.append("*💡 Tip: Add OPENAI_API_KEY for Reddit data and better triangulation.*")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# Reddit items
|
# Reddit items
|
||||||
|
|||||||
@@ -72,6 +72,61 @@ PROCESSING_MESSAGES = [
|
|||||||
"Organizing findings...",
|
"Organizing findings...",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
WEB_ONLY_MESSAGES = [
|
||||||
|
"Searching the web...",
|
||||||
|
"Finding blogs and docs...",
|
||||||
|
"Crawling news sites...",
|
||||||
|
"Discovering tutorials...",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Promo message for users without API keys
|
||||||
|
PROMO_MESSAGE = f"""
|
||||||
|
{Colors.YELLOW}{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.RESET}
|
||||||
|
{Colors.YELLOW}⚡ UNLOCK THE FULL POWER OF /last30days{Colors.RESET}
|
||||||
|
|
||||||
|
{Colors.DIM}Right now you're using web search only. Add API keys to unlock:{Colors.RESET}
|
||||||
|
|
||||||
|
{Colors.YELLOW}🟠 Reddit{Colors.RESET} - Real upvotes, comments, and community insights
|
||||||
|
└─ Add OPENAI_API_KEY (uses OpenAI's web_search for Reddit)
|
||||||
|
|
||||||
|
{Colors.CYAN}🔵 X (Twitter){Colors.RESET} - Real-time posts, likes, reposts from creators
|
||||||
|
└─ Add XAI_API_KEY (uses xAI's live X search)
|
||||||
|
|
||||||
|
{Colors.DIM}Setup:{Colors.RESET} Edit {Colors.BOLD}~/.config/last30days/.env{Colors.RESET}
|
||||||
|
{Colors.YELLOW}{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.RESET}
|
||||||
|
"""
|
||||||
|
|
||||||
|
PROMO_MESSAGE_PLAIN = """
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
⚡ UNLOCK THE FULL POWER OF /last30days
|
||||||
|
|
||||||
|
Right now you're using web search only. Add API keys to unlock:
|
||||||
|
|
||||||
|
🟠 Reddit - Real upvotes, comments, and community insights
|
||||||
|
└─ Add OPENAI_API_KEY (uses OpenAI's web_search for Reddit)
|
||||||
|
|
||||||
|
🔵 X (Twitter) - Real-time posts, likes, reposts from creators
|
||||||
|
└─ Add XAI_API_KEY (uses xAI's live X search)
|
||||||
|
|
||||||
|
Setup: Edit ~/.config/last30days/.env
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Shorter promo for single missing key
|
||||||
|
PROMO_SINGLE_KEY = {
|
||||||
|
"reddit": f"""
|
||||||
|
{Colors.DIM}💡 Tip: Add {Colors.YELLOW}OPENAI_API_KEY{Colors.RESET}{Colors.DIM} to ~/.config/last30days/.env for Reddit data with real engagement metrics!{Colors.RESET}
|
||||||
|
""",
|
||||||
|
"x": f"""
|
||||||
|
{Colors.DIM}💡 Tip: Add {Colors.CYAN}XAI_API_KEY{Colors.RESET}{Colors.DIM} to ~/.config/last30days/.env for X/Twitter data with real likes & reposts!{Colors.RESET}
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
|
||||||
|
PROMO_SINGLE_KEY_PLAIN = {
|
||||||
|
"reddit": "\n💡 Tip: Add OPENAI_API_KEY to ~/.config/last30days/.env for Reddit data with real engagement metrics!\n",
|
||||||
|
"x": "\n💡 Tip: Add XAI_API_KEY to ~/.config/last30days/.env for X/Twitter data with real likes & reposts!\n",
|
||||||
|
}
|
||||||
|
|
||||||
# Spinner frames
|
# Spinner frames
|
||||||
SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
||||||
DOTS_FRAMES = [' ', '. ', '.. ', '...']
|
DOTS_FRAMES = [' ', '. ', '.. ', '...']
|
||||||
@@ -214,6 +269,46 @@ class ProgressDisplay:
|
|||||||
sys.stderr.write(f"{Colors.RED}✗ Error:{Colors.RESET} {message}\n")
|
sys.stderr.write(f"{Colors.RED}✗ Error:{Colors.RESET} {message}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
def start_web_only(self):
|
||||||
|
"""Show web-only mode indicator."""
|
||||||
|
msg = random.choice(WEB_ONLY_MESSAGES)
|
||||||
|
self.spinner = Spinner(f"{Colors.GREEN}Web{Colors.RESET} {msg}", Colors.GREEN)
|
||||||
|
self.spinner.start()
|
||||||
|
|
||||||
|
def end_web_only(self):
|
||||||
|
"""End web-only spinner."""
|
||||||
|
if self.spinner:
|
||||||
|
self.spinner.stop(f"{Colors.GREEN}Web{Colors.RESET} Claude will search the web")
|
||||||
|
|
||||||
|
def show_web_only_complete(self):
|
||||||
|
"""Show completion for web-only mode."""
|
||||||
|
elapsed = time.time() - self.start_time
|
||||||
|
if IS_TTY:
|
||||||
|
sys.stderr.write(f"\n{Colors.GREEN}{Colors.BOLD}✓ Ready for web search{Colors.RESET} ")
|
||||||
|
sys.stderr.write(f"{Colors.DIM}({elapsed:.1f}s){Colors.RESET}\n")
|
||||||
|
sys.stderr.write(f" {Colors.GREEN}Web:{Colors.RESET} Claude will search blogs, docs & news\n\n")
|
||||||
|
else:
|
||||||
|
sys.stderr.write(f"✓ Ready for web search ({elapsed:.1f}s)\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
def show_promo(self, missing: str = "both"):
|
||||||
|
"""Show promotional message for missing API keys.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
missing: 'both', 'reddit', or 'x' - which keys are missing
|
||||||
|
"""
|
||||||
|
if missing == "both":
|
||||||
|
if IS_TTY:
|
||||||
|
sys.stderr.write(PROMO_MESSAGE)
|
||||||
|
else:
|
||||||
|
sys.stderr.write(PROMO_MESSAGE_PLAIN)
|
||||||
|
elif missing in PROMO_SINGLE_KEY:
|
||||||
|
if IS_TTY:
|
||||||
|
sys.stderr.write(PROMO_SINGLE_KEY[missing])
|
||||||
|
else:
|
||||||
|
sys.stderr.write(PROMO_SINGLE_KEY_PLAIN[missing])
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
|
||||||
def print_phase(phase: str, message: str):
|
def print_phase(phase: str, message: str):
|
||||||
"""Print a phase message."""
|
"""Print a phase message."""
|
||||||
|
|||||||
Reference in New Issue
Block a user