feat: add funding-digest skill + OpenClaw compatibility#15
Conversation
…n metadata for OpenClaw - New funding-digest skill: weekly digest of new programs with user preference filters (ecosystem, type, topic, budget) and cron setup for both Claude Code (CronCreate) and OpenClaw (cron.add with delivery channels) - Optimize find-funding-opportunities (v1.3.0): run community lookup + ecosystem + name queries in parallel in a single bash call instead of 3-4 sequential agent roundtrips - Flatten metadata in all SKILL.md files to single-line JSON for OpenClaw parser compatibility while maintaining Claude Code support - Update marketplace.json and README with new skill Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR introduces the new Funding Digest plugin to the marketplace, bumps Find Funding Opportunities to version 1.3.0, adds comprehensive documentation for the funding-digest skill, standardizes metadata formatting across skill files from YAML to JSON, and refactors find-funding-opportunities documentation to emphasize parallel execution. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.Change the |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
skills/funding-digest/SKILL.md (1)
75-88: Consider adding language specifiers to fenced code blocks.Static analysis flagged these template blocks as missing language specifiers. For the output format templates (lines 75-88, 94-100), consider using
markdownsince they represent formatted output. For the cron schedule example (lines 114-117), plain text is acceptable but could usetextfor explicitness.📝 Proposed fix
-``` +```markdown # Funding Digest — Week of {Mon DD}-``` +```markdown # Funding Digest — Week of {Mon DD} No new programs added this week{preference_summary}.-``` +```text Schedule: 0 9 * * 1 Prompt: Run the funding-digest skill — fetch and display new funding opportunities from the past weekAlso applies to: 94-100, 114-117
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@skills/funding-digest/SKILL.md` around lines 75 - 88, Update the fenced code blocks in SKILL.md to include language specifiers: add ```markdown for the output-format templates that start with the header "Funding Digest — Week of {Mon DD}" (both the filled and "No new programs" variants referenced around lines 75-100) and use ```text for the cron schedule block that contains "Schedule: 0 9 * * 1" (the example at ~114-117); ensure every triple-backtick fence in those examples is annotated accordingly so static analyzers recognize the block language.skills/find-funding-opportunities/SKILL.md (1)
118-118: Style: Avoid spaces at boundaries of inline code.Line 118 contains
`, `(comma-space in backticks). Consider removing the backticks and describing it as "comma-separated" or using`,`without the trailing space for cleaner markdown.✨ Suggested improvement
-- **Ecosystem**: `metadata.ecosystems` joined with `, ` (fall back to `communities[0].name`) +- **Ecosystem**: `metadata.ecosystems` joined with `,` (comma-separated) or fall back to `communities[0].name`As per static analysis hints: "Spaces inside code span elements (MD038)."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@skills/find-funding-opportunities/SKILL.md` at line 118, Update the inline code in the Ecosystem line to avoid spaces inside code spans: replace the backticked "`, ` " with either a description like "comma-separated" or a backticked comma without the trailing space (e.g., change the fragment in the line that reads "**Ecosystem**: `metadata.ecosystems` joined with `, ` (fall back to `communities[0].name`)” so the separator is written as "comma-separated" or `` `,` ``), ensuring no spaces inside the code span.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@skills/find-funding-opportunities/SKILL.md`:
- Around line 104-113: The fenced code block containing the output template
starting with "Found {count} programs (showing top 10):" in SKILL.md is missing
a language specifier; update the opening fence to include a language (e.g.,
change ``` to ```text or ```markdown) so the block becomes labeled (this
addresses MD040 and improves rendering/accessibility for the template output
shown in the Find Funding Opportunities skill).
- Around line 65-68: The curl commands write to fixed temp files
(/tmp/kf_communities.json, /tmp/kf_eco.json, /tmp/kf_name.json) causing race
conditions; change them to write into a unique temp directory (use INVOCATION_ID
to build TMP_DIR) and update all subsequent reads/merges to use "$TMP_DIR/..."
instead of /tmp/..., ensure the directory is created before use and removed
after use (rm -rf "$TMP_DIR") to clean up; update the curl invocations and wait
block to reference the new TMP_DIR variable so concurrent skill invocations do
not overwrite each other.
- Around line 71-78: The Python one-liner that sets COMMUNITY_UID interpolates
${NAME} directly into the inline script (see COMMUNITY_UID assignment and the
python snippet), which allows command injection; change the approach to pass the
ecosystem name as a safe command-line argument to Python (use sys.argv inside
the script rather than embedding ${NAME} into the source), read the JSON and
compare against sys.argv[1] (or argv[n]) to locate the matching community UID,
and invoke Python with the NAME as a separate argument instead of embedding it
in the quoted script.
---
Nitpick comments:
In `@skills/find-funding-opportunities/SKILL.md`:
- Line 118: Update the inline code in the Ecosystem line to avoid spaces inside
code spans: replace the backticked "`, ` " with either a description like
"comma-separated" or a backticked comma without the trailing space (e.g., change
the fragment in the line that reads "**Ecosystem**: `metadata.ecosystems` joined
with `, ` (fall back to `communities[0].name`)” so the separator is written as
"comma-separated" or `` `,` ``), ensuring no spaces inside the code span.
In `@skills/funding-digest/SKILL.md`:
- Around line 75-88: Update the fenced code blocks in SKILL.md to include
language specifiers: add ```markdown for the output-format templates that start
with the header "Funding Digest — Week of {Mon DD}" (both the filled and "No new
programs" variants referenced around lines 75-100) and use ```text for the cron
schedule block that contains "Schedule: 0 9 * * 1" (the example at ~114-117);
ensure every triple-backtick fence in those examples is annotated accordingly so
static analyzers recognize the block language.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 96c41f67-b498-4313-b274-e511b84e4426
📒 Files selected for processing (9)
.claude-plugin/marketplace.json.claude/skills/skill-creator/SKILL.mdREADME.mdskills/find-funding-opportunities/SKILL.mdskills/find-funding-opportunities/references/api-reference.mdskills/funding-digest/SKILL.mdskills/project-manager/SKILL.mdskills/setup-agent/SKILL.mdskills/skill-creator/SKILL.md
| curl -s "${H[@]}" "https://gapapi.karmahq.xyz/v2/communities?limit=100" > /tmp/kf_communities.json & | ||
| curl -s "${H[@]}" "${BASE}${EXTRA}&ecosystems=${NAME}" > /tmp/kf_eco.json & | ||
| curl -s "${H[@]}" "${BASE}${EXTRA}&name=${NAME}" > /tmp/kf_name.json & | ||
| wait |
There was a problem hiding this comment.
MAJOR: Race condition with fixed temporary filenames.
The temporary files use fixed names (/tmp/kf_communities.json, etc.) without incorporating $INVOCATION_ID. If multiple skill invocations run simultaneously, they will overwrite each other's temporary files, causing incorrect merged results.
♻️ Proposed fix incorporating INVOCATION_ID
# Run community lookup + ecosystems + name queries in parallel
-curl -s "${H[@]}" "https://gapapi.karmahq.xyz/v2/communities?limit=100" > /tmp/kf_communities.json &
-curl -s "${H[@]}" "${BASE}${EXTRA}&ecosystems=${NAME}" > /tmp/kf_eco.json &
-curl -s "${H[@]}" "${BASE}${EXTRA}&name=${NAME}" > /tmp/kf_name.json &
+TMP_DIR="/tmp/kf_${INVOCATION_ID}"
+mkdir -p "$TMP_DIR"
+curl -s "${H[@]}" "https://gapapi.karmahq.xyz/v2/communities?limit=100" > "$TMP_DIR/communities.json" &
+curl -s "${H[@]}" "${BASE}${EXTRA}&ecosystems=${NAME}" > "$TMP_DIR/eco.json" &
+curl -s "${H[@]}" "${BASE}${EXTRA}&name=${NAME}" > "$TMP_DIR/name.json" &
waitUpdate all subsequent references to use $TMP_DIR/ prefix, and add cleanup:
rm -rf "$TMP_DIR"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@skills/find-funding-opportunities/SKILL.md` around lines 65 - 68, The curl
commands write to fixed temp files (/tmp/kf_communities.json, /tmp/kf_eco.json,
/tmp/kf_name.json) causing race conditions; change them to write into a unique
temp directory (use INVOCATION_ID to build TMP_DIR) and update all subsequent
reads/merges to use "$TMP_DIR/..." instead of /tmp/..., ensure the directory is
created before use and removed after use (rm -rf "$TMP_DIR") to clean up; update
the curl invocations and wait block to reference the new TMP_DIR variable so
concurrent skill invocations do not overwrite each other.
| COMMUNITY_UID=$(python3 -c " | ||
| import json | ||
| data = json.load(open('/tmp/kf_communities.json')) | ||
| name = '${NAME}'.lower() | ||
| for c in data.get('payload', []): | ||
| if name in c.get('details', {}).get('name', '').lower(): | ||
| print(c['uid']); break | ||
| " 2>/dev/null) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify if there are other shell scripts in the repository that might have similar injection risks
rg -n -t md -C 2 'python3 -c.*\$\{[A-Z_]+\}'Repository: show-karma/skills
Length of output: 43
🏁 Script executed:
#!/bin/bash
# First, let's find and read the specific file mentioned in the review
find . -name "SKILL.md" -path "*/find-funding-opportunities/*" | head -5Repository: show-karma/skills
Length of output: 105
🏁 Script executed:
#!/bin/bash
# Get more context around the vulnerability
if [ -f "skills/find-funding-opportunities/SKILL.md" ]; then
sed -n '60,90p' "skills/find-funding-opportunities/SKILL.md" | cat -n
fiRepository: show-karma/skills
Length of output: 1460
🏁 Script executed:
#!/bin/bash
# Search more broadly for python3 -c patterns with variable interpolation in all files
rg 'python3.*-c.*\$\{' -A 3 -B 1 --max-count=20Repository: show-karma/skills
Length of output: 43
CRITICAL: Command injection vulnerability in Python snippet.
Line 15 interpolates ${NAME} directly into a Python string without escaping. If the ecosystem name contains a single quote, it breaks out of the string literal and executes arbitrary Python code.
Example attack: NAME="'; import os; os.system('malicious'); #"
Since this documentation guides AI agents constructing these commands from user input, proper escaping is essential. Use sys.argv to pass the variable safely:
Recommended fix
COMMUNITY_UID=$(python3 -c "
import json
+import sys
data = json.load(open('/tmp/kf_communities.json'))
-name = '${NAME}'.lower()
+name = sys.argv[1].lower()
for c in data.get('payload', []):
if name in c.get('details', {}).get('name', '').lower():
print(c['uid']); break
-" 2>/dev/null)
+" "$NAME" 2>/dev/null)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| COMMUNITY_UID=$(python3 -c " | |
| import json | |
| data = json.load(open('/tmp/kf_communities.json')) | |
| name = '${NAME}'.lower() | |
| for c in data.get('payload', []): | |
| if name in c.get('details', {}).get('name', '').lower(): | |
| print(c['uid']); break | |
| " 2>/dev/null) | |
| COMMUNITY_UID=$(python3 -c " | |
| import json | |
| import sys | |
| data = json.load(open('/tmp/kf_communities.json')) | |
| name = sys.argv[1].lower() | |
| for c in data.get('payload', []): | |
| if name in c.get('details', {}).get('name', '').lower(): | |
| print(c['uid']); break | |
| " "$NAME" 2>/dev/null) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@skills/find-funding-opportunities/SKILL.md` around lines 71 - 78, The Python
one-liner that sets COMMUNITY_UID interpolates ${NAME} directly into the inline
script (see COMMUNITY_UID assignment and the python snippet), which allows
command injection; change the approach to pass the ecosystem name as a safe
command-line argument to Python (use sys.argv inside the script rather than
embedding ${NAME} into the source), read the JSON and compare against
sys.argv[1] (or argv[n]) to locate the matching community UID, and invoke Python
with the NAME as a separate argument instead of embedding it in the quoted
script.
| ``` | ||
| Found 42 programs (showing top 10): | ||
|
|
||
| 1. **Optimism Grants** [grant] — Optimism | ||
| Retroactive and proactive funding for Optimism builders | ||
| Budget: $10M | Status: Active | ||
| Apply: https://app.charmverse.io/... | ||
|
|
||
| 2. **ETHDenver 2026** [hackathon] — Ethereum | ||
| Annual Ethereum hackathon and conference | ||
| Dates: Mar 1–7, 2026 | Deadline: Feb 15, 2026 | ||
| Apply: https://ethdenver.com/apply | ||
| Found {count} programs (showing top 10): | ||
|
|
||
| 3. **Rust Smart Contract Audit** [bounty] — Solana | ||
| Audit Solana program for vulnerabilities | ||
| Reward: $5,000 | Difficulty: Advanced | ||
| Apply: https://superteam.fun/... | ||
| 1. **{title}** [{type}] — {ecosystems} | ||
| {description, ~120 chars} | ||
| {detail line} | Deadline: {deadline or "Rolling"} | ||
| Apply: {submissionUrl} | ||
|
|
||
| Showing 10 of 42. Ask for more or narrow your search. | ||
| Showing 10 of {count}. Ask for more or narrow your search. | ||
| ``` |
There was a problem hiding this comment.
Add language specifier to fenced code block.
The code block at line 104 is missing a language identifier, which can affect rendering and accessibility. Since this is an output template, consider adding text or markdown after the opening fence.
📝 Proposed fix
-```
+```text
Found {count} programs (showing top 10):As per static analysis hints: "Fenced code blocks should have a language specified (MD040)."
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 104-104: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@skills/find-funding-opportunities/SKILL.md` around lines 104 - 113, The
fenced code block containing the output template starting with "Found {count}
programs (showing top 10):" in SKILL.md is missing a language specifier; update
the opening fence to include a language (e.g., change ``` to ```text or
```markdown) so the block becomes labeled (this addresses MD040 and improves
rendering/accessibility for the template output shown in the Find Funding
Opportunities skill).
Summary
funding-digestskill: weekly digest of new funding programs added to the Karma Funding Map in the past 7 days. Supports user preference filters (ecosystem, type, topic, budget) stored in platform-native memory (CLAUDE.md / USER.md). Includes cron setup for both Claude Code (CronCreate) and OpenClaw (cron.addwith delivery to Slack/Telegram/Discord)find-funding-opportunities(v1.2.0 → v1.3.0): ecosystem queries now run community lookup +ecosystems=+name=searches in parallel in a single bash call, then resolvecommunityUidsequentially. Cuts agent roundtrips from 3-4 to 1-2 while keeping the community list freshmetadatain all SKILL.md files to single-line JSON (metadata: {"author": "Karma", ...}) for OpenClaw parser compatibility. Claude Code is unaffected — it only usesname,description,version,tagsfrom frontmatterTest plan
find-funding-opportunitieswith an ecosystem query (e.g., "grants on Optimism") and verify parallel queries return merged, deduplicated resultsfind-funding-opportunitieswithout ecosystem (e.g., "hackathons") and verify single-request path worksfunding-digestmanually ("show me new funding this week") and verify digest outputclawhub publishwith flattened metadata format🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Updates