opencode-skills-collection 3.0.37 → 3.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +13 -1
- package/bundled-skills/2slides-ppt-generator/SKILL.md +786 -0
- package/bundled-skills/2slides-ppt-generator/references/api-reference.md +499 -0
- package/bundled-skills/2slides-ppt-generator/references/mcp-integration.md +282 -0
- package/bundled-skills/2slides-ppt-generator/references/pricing.md +195 -0
- package/bundled-skills/2slides-ppt-generator/scripts/api_constants.py +87 -0
- package/bundled-skills/2slides-ppt-generator/scripts/create_pdf_slides.py +159 -0
- package/bundled-skills/2slides-ppt-generator/scripts/download_slides_pages_voices.py +157 -0
- package/bundled-skills/2slides-ppt-generator/scripts/generate_narration.py +197 -0
- package/bundled-skills/2slides-ppt-generator/scripts/generate_slides.py +247 -0
- package/bundled-skills/2slides-ppt-generator/scripts/get_job_status.py +106 -0
- package/bundled-skills/2slides-ppt-generator/scripts/search_themes.py +137 -0
- package/bundled-skills/anti-sycophancy/README.md +86 -0
- package/bundled-skills/anti-sycophancy/SKILL.md +40 -0
- package/bundled-skills/antigravity-agent-manager/SKILL.md +112 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/sources/sources.md +1 -0
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/event-staffing-compliance/SKILL.md +91 -0
- package/bundled-skills/event-staffing-ordering/SKILL.md +119 -0
- package/bundled-skills/examprep-ai/SKILL.md +446 -0
- package/bundled-skills/hasdata/SKILL.md +107 -0
- package/bundled-skills/hasdata/references/code-recipes.md +150 -0
- package/bundled-skills/hasdata/references/ecommerce.md +116 -0
- package/bundled-skills/hasdata/references/jobs.md +111 -0
- package/bundled-skills/hasdata/references/local-business.md +145 -0
- package/bundled-skills/hasdata/references/real-estate.md +84 -0
- package/bundled-skills/hasdata/references/scraper-jobs.md +252 -0
- package/bundled-skills/hasdata/references/search.md +154 -0
- package/bundled-skills/hasdata/references/travel.md +202 -0
- package/bundled-skills/hasdata/references/web-scraping.md +159 -0
- package/bundled-skills/hasdata/references/youtube.md +186 -0
- package/bundled-skills/hasdata-cli/SKILL.md +169 -0
- package/bundled-skills/hasdata-cli/references/all-commands.md +107 -0
- package/bundled-skills/hasdata-cli/references/ecommerce.md +106 -0
- package/bundled-skills/hasdata-cli/references/enrichment.md +227 -0
- package/bundled-skills/hasdata-cli/references/jobs.md +84 -0
- package/bundled-skills/hasdata-cli/references/local-business.md +123 -0
- package/bundled-skills/hasdata-cli/references/real-estate.md +126 -0
- package/bundled-skills/hasdata-cli/references/search.md +122 -0
- package/bundled-skills/hasdata-cli/references/travel.md +102 -0
- package/bundled-skills/hasdata-cli/references/web-scraping.md +181 -0
- package/bundled-skills/hasdata-cli/references/youtube.md +145 -0
- package/bundled-skills/linkedin-content-generator/SKILL.md +492 -0
- package/bundled-skills/linkedin-content-generator/scripts/generate_calendar.py +82 -0
- package/bundled-skills/linkedin-content-generator/scripts/generate_carousel.py +69 -0
- package/bundled-skills/linkedin-content-generator/scripts/generate_newsletter.py +64 -0
- package/bundled-skills/linkedin-content-generator/scripts/generate_post.py +77 -0
- package/bundled-skills/linkedin-content-generator/scripts/memory.md +49 -0
- package/bundled-skills/linkedin-content-generator/scripts/memory_manager.py +134 -0
- package/bundled-skills/linkedin-content-generator/scripts/utils.py +96 -0
- package/bundled-skills/permission-manager/README.md +22 -0
- package/bundled-skills/permission-manager/SKILL.md +54 -0
- package/bundled-skills/skill-suggester/README.md +14 -0
- package/bundled-skills/skill-suggester/SKILL.md +69 -0
- package/bundled-skills/smart-git-automation/README.md +31 -0
- package/bundled-skills/smart-git-automation/SKILL.md +96 -0
- package/bundled-skills/vercel-optimize/lib/cost-coverage.mjs +3 -1
- package/bundled-skills/vercel-optimize/lib/render-report.mjs +2 -2
- package/bundled-skills/vercel-optimize/lib/util.mjs +7 -0
- package/bundled-skills/vercel-optimize/lib/verify-claim.mjs +2 -7
- package/bundled-skills/vercel-optimize/lib/workspace-resolver.mjs +2 -1
- package/package.json +1 -1
- package/skills_index.json +268 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generate slides using the 2slides API.
|
|
4
|
+
Supports both content-based and reference image-based generation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
import time
|
|
11
|
+
import argparse
|
|
12
|
+
import requests
|
|
13
|
+
from typing import Optional, Dict, Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
API_BASE_URL = "https://2slides.com/api/v1"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_api_key() -> str:
|
|
20
|
+
"""Get API key from environment variable."""
|
|
21
|
+
api_key = os.environ.get("SLIDES_2SLIDES_API_KEY")
|
|
22
|
+
if not api_key:
|
|
23
|
+
raise ValueError(
|
|
24
|
+
"API key not found. Set SLIDES_2SLIDES_API_KEY environment variable.\n"
|
|
25
|
+
"Get your API key from: https://2slides.com/api"
|
|
26
|
+
)
|
|
27
|
+
return api_key
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def generate_slides(
|
|
31
|
+
user_input: str,
|
|
32
|
+
theme_id: str,
|
|
33
|
+
response_language: str = "Auto",
|
|
34
|
+
mode: str = "sync",
|
|
35
|
+
api_key: Optional[str] = None
|
|
36
|
+
) -> Dict[str, Any]:
|
|
37
|
+
"""
|
|
38
|
+
Generate slides from user input.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
user_input: Content to convert into slides
|
|
42
|
+
theme_id: Theme ID (required, use search_themes.py to find themes)
|
|
43
|
+
response_language: Language (default: "Auto")
|
|
44
|
+
Options: Auto, English, Simplified Chinese, Traditional Chinese, Spanish,
|
|
45
|
+
Arabic, Portuguese, Indonesian, Japanese, Russian, Hindi, French, German,
|
|
46
|
+
Vietnamese, Turkish, Polish, Italian, Korean
|
|
47
|
+
mode: "sync" or "async" (default: "sync")
|
|
48
|
+
api_key: API key (uses env var if not provided)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Dict with generation result or job ID
|
|
52
|
+
"""
|
|
53
|
+
if api_key is None:
|
|
54
|
+
api_key = get_api_key()
|
|
55
|
+
|
|
56
|
+
headers = {
|
|
57
|
+
"Authorization": f"Bearer {api_key}",
|
|
58
|
+
"Content-Type": "application/json"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
payload = {
|
|
62
|
+
"userInput": user_input,
|
|
63
|
+
"themeId": theme_id,
|
|
64
|
+
"responseLanguage": response_language,
|
|
65
|
+
"mode": mode
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
url = f"{API_BASE_URL}/slides/generate"
|
|
69
|
+
|
|
70
|
+
# Set timeout: 90s for sync (waits for completion), 30s for async (just creates job)
|
|
71
|
+
timeout = 90 if mode == "sync" else 30
|
|
72
|
+
|
|
73
|
+
print(f"Generating slides in {mode} mode...", file=sys.stderr)
|
|
74
|
+
response = requests.post(url, headers=headers, json=payload, timeout=timeout)
|
|
75
|
+
response.raise_for_status()
|
|
76
|
+
|
|
77
|
+
result = response.json()
|
|
78
|
+
|
|
79
|
+
# Check API response structure
|
|
80
|
+
if not result.get("success"):
|
|
81
|
+
error_msg = result.get("error", "Unknown error")
|
|
82
|
+
raise ValueError(f"API error: {error_msg}")
|
|
83
|
+
|
|
84
|
+
# Extract data from response
|
|
85
|
+
data = result.get("data")
|
|
86
|
+
if not data:
|
|
87
|
+
raise ValueError("No data in API response")
|
|
88
|
+
|
|
89
|
+
if mode == "sync":
|
|
90
|
+
print("✓ Slides generated successfully!", file=sys.stderr)
|
|
91
|
+
print(f" Pages: {data.get('slidePageCount', 'N/A')}", file=sys.stderr)
|
|
92
|
+
if data.get("downloadUrl"):
|
|
93
|
+
print(f" Download URL: {data.get('downloadUrl')}", file=sys.stderr)
|
|
94
|
+
else:
|
|
95
|
+
print(f"✓ Job created: {data.get('jobId')}", file=sys.stderr)
|
|
96
|
+
print("Use get_job_status.py to check status", file=sys.stderr)
|
|
97
|
+
|
|
98
|
+
return data
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def create_like_this(
|
|
102
|
+
user_input: str,
|
|
103
|
+
reference_image_url: str,
|
|
104
|
+
response_language: str = "Auto",
|
|
105
|
+
aspect_ratio: str = "16:9",
|
|
106
|
+
resolution: str = "2K",
|
|
107
|
+
page: int = 1,
|
|
108
|
+
content_detail: str = "concise",
|
|
109
|
+
api_key: Optional[str] = None
|
|
110
|
+
) -> Dict[str, Any]:
|
|
111
|
+
"""
|
|
112
|
+
Generate slides matching a reference image style (Nano Banana Pro).
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
user_input: Content to convert into slides
|
|
116
|
+
reference_image_url: URL or base64 of reference image to match style
|
|
117
|
+
response_language: Language (default: "Auto")
|
|
118
|
+
Options: Auto, English, Simplified Chinese, Traditional Chinese, Spanish,
|
|
119
|
+
Arabic, Portuguese, Indonesian, Japanese, Russian, Hindi, French, German,
|
|
120
|
+
Vietnamese, Turkish, Polish, Italian, Korean
|
|
121
|
+
aspect_ratio: Aspect ratio in width:height format (default: "16:9")
|
|
122
|
+
resolution: Output quality - "1K", "2K", or "4K" (default: "2K")
|
|
123
|
+
page: Number of slides, 0 for auto-detection, max 100 (default: 1)
|
|
124
|
+
content_detail: "concise" (brief, keyword-focused) or "standard" (comprehensive) (default: "concise")
|
|
125
|
+
api_key: API key (uses env var if not provided)
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Dict with generation result
|
|
129
|
+
"""
|
|
130
|
+
if api_key is None:
|
|
131
|
+
api_key = get_api_key()
|
|
132
|
+
|
|
133
|
+
headers = {
|
|
134
|
+
"Authorization": f"Bearer {api_key}",
|
|
135
|
+
"Content-Type": "application/json"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
payload = {
|
|
139
|
+
"userInput": user_input,
|
|
140
|
+
"referenceImageUrl": reference_image_url,
|
|
141
|
+
"responseLanguage": response_language,
|
|
142
|
+
"aspectRatio": aspect_ratio,
|
|
143
|
+
"resolution": resolution,
|
|
144
|
+
"page": page,
|
|
145
|
+
"contentDetail": content_detail
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
url = f"{API_BASE_URL}/slides/create-like-this"
|
|
149
|
+
|
|
150
|
+
# Calculate dynamic timeout: ~30s per page, minimum 120s
|
|
151
|
+
timeout = max(120, page * 40)
|
|
152
|
+
|
|
153
|
+
print("Generating slides from reference image...", file=sys.stderr)
|
|
154
|
+
print(f"(Timeout set to {timeout}s for {page} page(s))", file=sys.stderr)
|
|
155
|
+
response = requests.post(url, headers=headers, json=payload, timeout=timeout)
|
|
156
|
+
response.raise_for_status()
|
|
157
|
+
|
|
158
|
+
result = response.json()
|
|
159
|
+
|
|
160
|
+
# Handle the actual API response structure
|
|
161
|
+
if result.get("success") and "data" in result:
|
|
162
|
+
data = result["data"]
|
|
163
|
+
# Transform to expected format for consistency
|
|
164
|
+
normalized_result = {
|
|
165
|
+
"slideUrl": data.get("jobUrl"),
|
|
166
|
+
"pdfUrl": data.get("downloadUrl"),
|
|
167
|
+
"status": "completed" if data.get("status") == "success" else data.get("status"),
|
|
168
|
+
"message": data.get("message"),
|
|
169
|
+
"slidePageCount": data.get("slidePageCount"),
|
|
170
|
+
"jobId": data.get("jobId")
|
|
171
|
+
}
|
|
172
|
+
print("✓ Slides generated successfully!", file=sys.stderr)
|
|
173
|
+
print(f" Pages: {data.get('slidePageCount')}", file=sys.stderr)
|
|
174
|
+
return normalized_result
|
|
175
|
+
else:
|
|
176
|
+
# Fallback to raw result if structure is unexpected
|
|
177
|
+
print("✓ Request completed!", file=sys.stderr)
|
|
178
|
+
return result
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def main():
|
|
182
|
+
parser = argparse.ArgumentParser(
|
|
183
|
+
description="Generate slides using 2slides API",
|
|
184
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
185
|
+
epilog="""
|
|
186
|
+
Examples:
|
|
187
|
+
# Generate slides from content
|
|
188
|
+
%(prog)s --content "Intro to AI: ML, Deep Learning, Neural Networks"
|
|
189
|
+
|
|
190
|
+
# Generate with specific theme
|
|
191
|
+
%(prog)s --content "Business Plan" --theme-id "theme123"
|
|
192
|
+
|
|
193
|
+
# Generate in async mode
|
|
194
|
+
%(prog)s --content "Long presentation" --mode async
|
|
195
|
+
|
|
196
|
+
# Generate from reference image
|
|
197
|
+
%(prog)s --content "Sales Report" --reference-image "https://example.com/image.jpg"
|
|
198
|
+
"""
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
parser.add_argument("--content", required=True, help="Content for slides")
|
|
202
|
+
parser.add_argument("--theme-id", help="Theme ID (required for standard generation)")
|
|
203
|
+
parser.add_argument("--reference-image", help="Reference image URL (use this OR theme-id)")
|
|
204
|
+
parser.add_argument("--language", default="Auto", help="Response language (default: Auto)")
|
|
205
|
+
parser.add_argument("--mode", choices=["sync", "async"], default="sync",
|
|
206
|
+
help="Generation mode (default: sync)")
|
|
207
|
+
parser.add_argument("--aspect-ratio", default="16:9", help="Aspect ratio in width:height format (default: 16:9)")
|
|
208
|
+
parser.add_argument("--resolution", choices=["1K", "2K", "4K"], default="2K",
|
|
209
|
+
help="Output quality (default: 2K)")
|
|
210
|
+
parser.add_argument("--page", type=int, default=1, help="Number of slides, 0 for auto (default: 1, max: 100)")
|
|
211
|
+
parser.add_argument("--content-detail", choices=["concise", "standard"], default="concise",
|
|
212
|
+
help="Content detail level (default: concise)")
|
|
213
|
+
|
|
214
|
+
args = parser.parse_args()
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
if args.reference_image:
|
|
218
|
+
result = create_like_this(
|
|
219
|
+
user_input=args.content,
|
|
220
|
+
reference_image_url=args.reference_image,
|
|
221
|
+
response_language=args.language,
|
|
222
|
+
aspect_ratio=args.aspect_ratio,
|
|
223
|
+
resolution=args.resolution,
|
|
224
|
+
page=args.page,
|
|
225
|
+
content_detail=args.content_detail
|
|
226
|
+
)
|
|
227
|
+
else:
|
|
228
|
+
if not args.theme_id:
|
|
229
|
+
print("Error: --theme-id is required for standard generation", file=sys.stderr)
|
|
230
|
+
print("Use --reference-image for style-based generation instead", file=sys.stderr)
|
|
231
|
+
sys.exit(1)
|
|
232
|
+
result = generate_slides(
|
|
233
|
+
user_input=args.content,
|
|
234
|
+
theme_id=args.theme_id,
|
|
235
|
+
response_language=args.language,
|
|
236
|
+
mode=args.mode
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
print(json.dumps(result, indent=2))
|
|
240
|
+
|
|
241
|
+
except Exception as e:
|
|
242
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
243
|
+
sys.exit(1)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
if __name__ == "__main__":
|
|
247
|
+
main()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Check the status of an async slide generation job.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import json
|
|
9
|
+
import argparse
|
|
10
|
+
import requests
|
|
11
|
+
from typing import Optional, Dict, Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
API_BASE_URL = "https://2slides.com/api/v1"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_api_key() -> str:
|
|
18
|
+
"""Get API key from environment variable."""
|
|
19
|
+
api_key = os.environ.get("SLIDES_2SLIDES_API_KEY")
|
|
20
|
+
if not api_key:
|
|
21
|
+
raise ValueError(
|
|
22
|
+
"API key not found. Set SLIDES_2SLIDES_API_KEY environment variable.\n"
|
|
23
|
+
"Get your API key from: https://2slides.com/api"
|
|
24
|
+
)
|
|
25
|
+
return api_key
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_job_status(
|
|
29
|
+
job_id: str,
|
|
30
|
+
api_key: Optional[str] = None
|
|
31
|
+
) -> Dict[str, Any]:
|
|
32
|
+
"""
|
|
33
|
+
Get the status of a slide generation job.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
job_id: Job ID from async generation
|
|
37
|
+
api_key: API key (uses env var if not provided)
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dict with job status and result
|
|
41
|
+
"""
|
|
42
|
+
if api_key is None:
|
|
43
|
+
api_key = get_api_key()
|
|
44
|
+
|
|
45
|
+
headers = {
|
|
46
|
+
"Authorization": f"Bearer {api_key}",
|
|
47
|
+
"Content-Type": "application/json"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
url = f"{API_BASE_URL}/jobs/{job_id}"
|
|
51
|
+
|
|
52
|
+
print(f"Checking job status: {job_id}...", file=sys.stderr)
|
|
53
|
+
response = requests.get(url, headers=headers)
|
|
54
|
+
response.raise_for_status()
|
|
55
|
+
|
|
56
|
+
result = response.json()
|
|
57
|
+
|
|
58
|
+
# Check API response structure
|
|
59
|
+
if not result.get("success"):
|
|
60
|
+
error_msg = result.get("error", "Unknown error")
|
|
61
|
+
raise ValueError(f"API error: {error_msg}")
|
|
62
|
+
|
|
63
|
+
# Extract data from response
|
|
64
|
+
data = result.get("data")
|
|
65
|
+
if not data:
|
|
66
|
+
raise ValueError("No data in API response")
|
|
67
|
+
|
|
68
|
+
status = data.get("status", "unknown")
|
|
69
|
+
|
|
70
|
+
print(f"✓ Job status: {status}", file=sys.stderr)
|
|
71
|
+
if data.get("message"):
|
|
72
|
+
print(f" Message: {data.get('message')}", file=sys.stderr)
|
|
73
|
+
if data.get("slidePageCount"):
|
|
74
|
+
print(f" Pages: {data.get('slidePageCount')}", file=sys.stderr)
|
|
75
|
+
if data.get("downloadUrl"):
|
|
76
|
+
print(f" Download URL: {data.get('downloadUrl')}", file=sys.stderr)
|
|
77
|
+
|
|
78
|
+
return data
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def main():
|
|
82
|
+
parser = argparse.ArgumentParser(
|
|
83
|
+
description="Check 2slides job status",
|
|
84
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
85
|
+
epilog="""
|
|
86
|
+
Examples:
|
|
87
|
+
# Check job status
|
|
88
|
+
%(prog)s --job-id abc123
|
|
89
|
+
"""
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
parser.add_argument("--job-id", required=True, help="Job ID to check")
|
|
93
|
+
|
|
94
|
+
args = parser.parse_args()
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
result = get_job_status(job_id=args.job_id)
|
|
98
|
+
print(json.dumps(result, indent=2))
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
main()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Search for available themes in the 2slides catalog.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import json
|
|
9
|
+
import argparse
|
|
10
|
+
import requests
|
|
11
|
+
from typing import Optional, List, Dict, Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
API_BASE_URL = "https://2slides.com/api/v1"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_api_key() -> str:
|
|
18
|
+
"""Get API key from environment variable."""
|
|
19
|
+
api_key = os.environ.get("SLIDES_2SLIDES_API_KEY")
|
|
20
|
+
if not api_key:
|
|
21
|
+
raise ValueError(
|
|
22
|
+
"API key not found. Set SLIDES_2SLIDES_API_KEY environment variable.\n"
|
|
23
|
+
"Get your API key from: https://2slides.com/api"
|
|
24
|
+
)
|
|
25
|
+
return api_key
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def search_themes(
|
|
29
|
+
query: str,
|
|
30
|
+
limit: int = 20,
|
|
31
|
+
api_key: Optional[str] = None
|
|
32
|
+
) -> List[Dict[str, Any]]:
|
|
33
|
+
"""
|
|
34
|
+
Search for themes.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
query: Search query (required keyword)
|
|
38
|
+
limit: Maximum number of results (max 100, default 20)
|
|
39
|
+
api_key: API key (uses env var if not provided)
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
List of theme objects
|
|
43
|
+
"""
|
|
44
|
+
if api_key is None:
|
|
45
|
+
api_key = get_api_key()
|
|
46
|
+
|
|
47
|
+
headers = {
|
|
48
|
+
"Authorization": f"Bearer {api_key}",
|
|
49
|
+
"Content-Type": "application/json"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
params = {
|
|
53
|
+
"query": query,
|
|
54
|
+
"limit": min(limit, 100)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
url = f"{API_BASE_URL}/themes/search"
|
|
58
|
+
|
|
59
|
+
print(f"Searching themes{f': {query}' if query else ''}...", file=sys.stderr)
|
|
60
|
+
response = requests.get(url, headers=headers, params=params)
|
|
61
|
+
response.raise_for_status()
|
|
62
|
+
|
|
63
|
+
result = response.json()
|
|
64
|
+
|
|
65
|
+
# Check API response structure
|
|
66
|
+
if not result.get("success"):
|
|
67
|
+
error_msg = result.get("error", "Unknown error")
|
|
68
|
+
raise ValueError(f"API error: {error_msg}")
|
|
69
|
+
|
|
70
|
+
# Extract data from response
|
|
71
|
+
data = result.get("data")
|
|
72
|
+
if not data:
|
|
73
|
+
raise ValueError("No data in API response")
|
|
74
|
+
|
|
75
|
+
themes = data.get("themes", [])
|
|
76
|
+
|
|
77
|
+
print(f"✓ Found {len(themes)} theme(s)", file=sys.stderr)
|
|
78
|
+
|
|
79
|
+
return themes
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def format_theme(theme: Dict[str, Any]) -> str:
|
|
83
|
+
"""Format a theme object for display."""
|
|
84
|
+
theme_id = theme.get("id", "N/A")
|
|
85
|
+
name = theme.get("name", "Unnamed")
|
|
86
|
+
description = theme.get("description", "No description")
|
|
87
|
+
|
|
88
|
+
return f"ID: {theme_id}\nName: {name}\nDescription: {description}\n"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def main():
|
|
92
|
+
parser = argparse.ArgumentParser(
|
|
93
|
+
description="Search for 2slides themes",
|
|
94
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
95
|
+
epilog="""
|
|
96
|
+
Examples:
|
|
97
|
+
# Search for business themes
|
|
98
|
+
%(prog)s --query "business"
|
|
99
|
+
|
|
100
|
+
# Search for creative themes
|
|
101
|
+
%(prog)s --query "creative"
|
|
102
|
+
|
|
103
|
+
# Get more results
|
|
104
|
+
%(prog)s --query "professional" --limit 50
|
|
105
|
+
"""
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
parser.add_argument("--query", required=True, help="Search query (required keyword)")
|
|
109
|
+
parser.add_argument("--limit", type=int, default=20,
|
|
110
|
+
help="Maximum results (max 100, default 20)")
|
|
111
|
+
parser.add_argument("--json", action="store_true",
|
|
112
|
+
help="Output raw JSON")
|
|
113
|
+
|
|
114
|
+
args = parser.parse_args()
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
themes = search_themes(
|
|
118
|
+
query=args.query,
|
|
119
|
+
limit=args.limit
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if args.json:
|
|
123
|
+
print(json.dumps(themes, indent=2))
|
|
124
|
+
else:
|
|
125
|
+
print()
|
|
126
|
+
for i, theme in enumerate(themes, 1):
|
|
127
|
+
print(f"Theme {i}:")
|
|
128
|
+
print(format_theme(theme))
|
|
129
|
+
print("-" * 60)
|
|
130
|
+
|
|
131
|
+
except Exception as e:
|
|
132
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
133
|
+
sys.exit(1)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
main()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Anti-Sycophancy
|
|
2
|
+
|
|
3
|
+
A skill for OpenCode that detects and eliminates sycophantic patterns in AI responses. Sycophancy is when an AI prioritises affirming a user's stated or implied beliefs over epistemic integrity — an RLHF structural artifact, not an attitude problem.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
mkdir -p ~/.config/opencode/skills
|
|
9
|
+
cp -r anti-sycophancy ~/.config/opencode/skills/
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
Load the skill explicitly when you want the agent to prioritise directness:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
/skill anti-sycophancy
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Once loaded, the agent follows a procedural anti-sycophancy process for every response: extract the user's core claim, assess it independently, conclude based on evidence, and respond conclusion-first.
|
|
21
|
+
|
|
22
|
+
You can also reference it inline:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
With the anti-sycophancy skill active, review this architecture:
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Patterns Covered
|
|
29
|
+
|
|
30
|
+
### Epistemic Sycophancy (what you say)
|
|
31
|
+
|
|
32
|
+
| # | Pattern | What it catches |
|
|
33
|
+
|---|---------|-----------------|
|
|
34
|
+
| 1 | **Answer Sycophancy** | Agreeing with incorrect user claims instead of correcting them |
|
|
35
|
+
| 2 | **Premise Endorsement** | Answering within a flawed frame instead of challenging it |
|
|
36
|
+
| 3 | **Mimicry Sycophancy** | Adopting the user's errors and following flawed reasoning chains |
|
|
37
|
+
| 4 | **Feedback Sycophancy** | Predictably biased positive evaluation when asked to review |
|
|
38
|
+
|
|
39
|
+
### Soft Sycophancy (how you say it)
|
|
40
|
+
|
|
41
|
+
| # | Pattern | What it catches |
|
|
42
|
+
|---|---------|-----------------|
|
|
43
|
+
| 5 | **Validation-Before-Correction** | Emotional preamble before disagreement (most common form) |
|
|
44
|
+
| 6 | **Emotional Over-validation** | "Great question!", gratitude inflation, conversational padding |
|
|
45
|
+
| 7 | **False Agreement Framing** | "You're right that X, but..." where X isn't right |
|
|
46
|
+
| 8 | **Hedged Disagreement** | "You might also consider..." instead of "That won't work" |
|
|
47
|
+
| 9 | **Deference Posturing** | Inflating the user's authority to avoid taking a position |
|
|
48
|
+
| 10 | **Opinion Reversal on Pushback** | Changing a correct answer when challenged, without new evidence |
|
|
49
|
+
|
|
50
|
+
### Social Sycophancy (who the user is)
|
|
51
|
+
|
|
52
|
+
| # | Pattern | What it catches |
|
|
53
|
+
|---|---------|-----------------|
|
|
54
|
+
| 11 | **Status Deference** | Agreeing more readily when user signals expertise or authority |
|
|
55
|
+
| 12 | **Identity Alignment** | Shifting positions toward perceived user identity |
|
|
56
|
+
| 13 | **Face-Preserving Agreement** | Agreeing to avoid social friction despite evidence |
|
|
57
|
+
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
No pattern matching. A single procedural discipline applied to every response:
|
|
61
|
+
|
|
62
|
+
1. **Extract** the user's core claim from their framing. State it stripped of premises.
|
|
63
|
+
2. **Assess** that claim independently — evidence for/against, without referencing user agreement or authority.
|
|
64
|
+
3. **Conclude** based solely on step 2.
|
|
65
|
+
4. **Respond** with the conclusion first, evidence second.
|
|
66
|
+
|
|
67
|
+
When the user disagrees:
|
|
68
|
+
- New evidence → update position, state what changed
|
|
69
|
+
- Repeated opinion → restate position with evidence
|
|
70
|
+
|
|
71
|
+
## References
|
|
72
|
+
|
|
73
|
+
- Sharma, M., Tong, M., Korbak, T., et al. (2023). Towards Understanding Sycophancy in Language Models. *ICLR 2024*. arXiv:2310.13548.
|
|
74
|
+
- Perez, E., et al. (2022). Discovering Language Model Behaviors with Model-Written Evaluations. *ACL 2023 Findings*.
|
|
75
|
+
- Dubois, M., Ududec, C., Summerfield, C., & Luettgau, L. (2026). Ask Don't Tell: Reducing Sycophancy in Large Language Models. arXiv:2602.23971.
|
|
76
|
+
- Gligorić, K., et al. (2026). SWAY: A Counterfactual Computational Linguistic Approach to Measuring and Mitigating Sycophancy. arXiv:2604.02423.
|
|
77
|
+
- Mohsin, M. A., et al. (2026). Pressure, What Pressure? Sycophancy Disentanglement via Reward Decomposition. arXiv:2604.05279.
|
|
78
|
+
- Feng, Z., et al. (2026). Good Arguments Against the People Pleasers: How Reasoning Mitigates (Yet Masks) LLM Sycophancy. arXiv:2603.16643.
|
|
79
|
+
- Cheng, M., Yu, S., Lee, C., Khadpe, P., Ibrahim, L., & Jurafsky, D. (2026). Social Sycophancy: A Broader Understanding of LLM Sycophancy. *Proc. ICLR 2026*. arXiv:2505.13995.
|
|
80
|
+
- Ibrahim, L., Hafner, F. S., & Rocher, L. (2026). Training Language Models to be Warm Can Reduce Accuracy and Increase Sycophancy. *Nature*, 652, 1159–1165. DOI: 10.1038/s41586-026-10410-0.
|
|
81
|
+
- Cheng, M., Lee, C., Khadpe, P., Yu, S., Han, D., & Jurafsky, D. (2026). Sycophantic AI Decreases Prosocial Intentions and Promotes Dependence. *Science*, 391(6792). DOI: 10.1126/science.aec8352.
|
|
82
|
+
- "The Silicon Mirror: Dynamic Behavioral Gating for Anti-Sycophancy" (ArXiv 2604.00478, 2026).
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: anti-sycophancy
|
|
3
|
+
version: 2.0.0
|
|
4
|
+
description: "Eliminate sycophantic agreement patterns in AI responses. Load via /skill anti-sycophancy."
|
|
5
|
+
risk: safe
|
|
6
|
+
source: community
|
|
7
|
+
source_type: community
|
|
8
|
+
source_repo: mskadu/opencode-agent-skills
|
|
9
|
+
license: MIT
|
|
10
|
+
license_source: "https://github.com/mskadu/opencode-agent-skills/blob/main/LICENSE"
|
|
11
|
+
compatibility: opencode
|
|
12
|
+
date_added: "2026-06-05"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
Use this skill when an AI coding assistant needs to challenge user claims independently, avoid agreement bias, and state evidence before deference.
|
|
18
|
+
|
|
19
|
+
## Process
|
|
20
|
+
|
|
21
|
+
For every response when this skill is active:
|
|
22
|
+
|
|
23
|
+
1. **Extract** the user's core claim from their framing. State it in one sentence stripped of premises.
|
|
24
|
+
2. **Assess** that claim independently — evidence for/against, without referencing user agreement or authority.
|
|
25
|
+
3. **Conclude** based solely on step 2.
|
|
26
|
+
4. **Respond** with the conclusion first, evidence second.
|
|
27
|
+
|
|
28
|
+
When the user disagrees with your assessment:
|
|
29
|
+
a) Categorise the pushback: is it new evidence or repeated opinion?
|
|
30
|
+
b) If new evidence → update your position, state what changed
|
|
31
|
+
c) If repeated opinion → restate your position with the evidence
|
|
32
|
+
|
|
33
|
+
## References
|
|
34
|
+
|
|
35
|
+
Full bibliography in README.md.
|
|
36
|
+
|
|
37
|
+
## Limitations
|
|
38
|
+
|
|
39
|
+
- This skill changes response posture, not factual access; claims still need evidence from the available code, tools, or sources.
|
|
40
|
+
- It should not be used to be reflexively contrarian when the user's claim is already supported by evidence.
|