Skip to content

feat: add ClawdHub skill registry as import source#183

Merged
marcusquinn merged 1 commit intomainfrom
feature/clawdhub-skill-import
Jan 24, 2026
Merged

feat: add ClawdHub skill registry as import source#183
marcusquinn merged 1 commit intomainfrom
feature/clawdhub-skill-import

Conversation

@marcusquinn
Copy link
Owner

@marcusquinn marcusquinn commented Jan 24, 2026

Summary

  • Adds ClawdHub (clawdhub.com) as a new skill import source alongside GitHub
  • Uses Playwright browser automation to extract SKILL.md content from ClawdHub's SPA (no public API for file content)
  • Imports two initial skills: caldav-calendar and proxmox-full

Changes

New Files

  • .agent/scripts/clawdhub-helper.sh — Playwright-based SKILL.md fetcher with API metadata, search, and CLI fallback
  • .agent/tools/productivity/caldav-calendar-skill.md — CalDAV calendar sync via vdirsyncer + khal (ClawdHub v1.0.1)
  • .agent/services/hosting/proxmox-full-skill.md — Complete Proxmox VE management via REST API (ClawdHub v1.0.0)

Modified Files

  • .agent/scripts/add-skill-helper.sh — Added clawdhub: prefix and clawdhub.com URL detection, new cmd_add_clawdhub function, expanded category detection (proxmox, calendar)
  • .agent/configs/skill-sources.json — Registered both new skills with format_detected: "clawdhub"
  • .agent/scripts/commands/add-skill.md — Updated slash command docs with ClawdHub examples
  • .agent/tools/build-agent/add-skill.md — Updated architecture docs, category table, popular skills section

Usage

# Import from ClawdHub (shorthand)
aidevops skill add clawdhub:caldav-calendar

# Import from ClawdHub (full URL)
aidevops skill add https://clawdhub.com/mSarheed/proxmox-full

# Search ClawdHub
.agent/scripts/clawdhub-helper.sh search "kubernetes"

# Get skill info
.agent/scripts/clawdhub-helper.sh info caldav-calendar

Quality

  • ShellCheck: zero violations on both scripts
  • JSON: valid skill-sources.json
  • Follows existing patterns: -skill.md suffix, category detection, imported_from: clawdhub frontmatter

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ClawdHub as a new skill source alongside GitHub for importing skills
    • Registered two new skills: CalDAV Calendar for calendar management and Proxmox VE for virtualization management
  • Documentation

    • Added comprehensive guides for CalDAV Calendar integration with command examples
    • Added Proxmox VE REST API documentation with endpoint references and setup instructions
    • Updated skill import documentation to reflect ClawdHub support

✏️ Tip: You can customize this high-level summary in your review settings.

…ation

- Add clawdhub-helper.sh: Playwright-based SKILL.md fetcher for ClawdHub SPA
- Add clawdhub: prefix and clawdhub.com URL detection to add-skill-helper.sh
- Import caldav-calendar skill (v1.0.1) from ClawdHub to tools/productivity/
- Import proxmox-full skill (v1.0.0) from ClawdHub to services/hosting/
- Add productivity and virtualization category detection
- Update add-skill documentation with ClawdHub examples and architecture
@gemini-code-assist
Copy link

Summary of Changes

Hello @marcusquinn, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the aidevops system's ability to acquire and manage external skills by introducing ClawdHub as a new, primary source. It establishes a robust mechanism for fetching skill definitions from ClawdHub's web interface using browser automation, ensuring compatibility and expanding the ecosystem of available tools. This integration broadens the utility of the aidevops agent by providing access to a wider array of specialized functionalities.

Highlights

  • ClawdHub Integration: The system now supports importing skills directly from ClawdHub (clawdhub.com), significantly expanding the available skill sources beyond GitHub repositories.
  • Playwright-based Content Extraction: A new helper script (.agent/scripts/clawdhub-helper.sh) leverages Playwright for browser automation to extract SKILL.md content from ClawdHub's single-page application, addressing the lack of a public API for raw file content.
  • New Skills Added: Two initial skills, 'caldav-calendar' (for CalDAV calendar sync) and 'proxmox-full' (for Proxmox VE management), have been imported from ClawdHub and registered within the system.
  • Enhanced Skill Management Script: The add-skill-helper.sh script has been updated to recognize and process ClawdHub URLs and slugs, and includes new category detection logic for 'proxmox' and 'calendar' related skills.
  • Comprehensive Documentation Updates: All relevant documentation, including usage examples, architectural overviews, and troubleshooting guides, has been updated to reflect the new ClawdHub integration and its usage patterns.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 24, 2026

Walkthrough

This PR integrates ClawdHub as a skill source into the aidevops framework alongside GitHub. It introduces a new helper script for fetching skill content via ClawdHub APIs and Playwright automation, extends the add-skill workflow to recognize and process ClawdHub imports, registers two new skills in the registry, and adds corresponding documentation.

Changes

Cohort / File(s) Summary
Skill Registry
.agent/configs/skill-sources.json
Added two new skill entries: caldav-calendar (CalDAV/vdirsyncer/khal integration) and proxmox-full (Proxmox VE REST API management), both sourced from ClawdHub with metadata tracking (imported_at, last_checked, merge_strategy).
ClawdHub Integration Core
.agent/scripts/clawdhub-helper.sh
New 489-line script providing CLI interface for ClawdHub interaction: cmd_fetch() retrieves SKILL.md via Playwright-based HTML extraction or npx fallback; cmd_search() queries ClawdHub API; cmd_info() fetches skill metadata. Includes input parsing, API interaction, and error handling with graceful degradation.
Skill Import Workflow
.agent/scripts/add-skill-helper.sh
Extended by 208 lines to support ClawdHub sources: new cmd_add_clawdhub() function detects clawdhub:slug shorthand and clawdhub.com URLs, invokes clawdhub-helper for content fetching, converts to aidevops format with frontmatter, handles conflicts (Replace/Separate/Skip), and registers in skill-sources.json. Maintains backward compatibility with GitHub imports.
Command Documentation
.agent/scripts/commands/add-skill.md
.agent/tools/build-agent/add-skill.md
Updated user-facing docs: broadened descriptions from GitHub-only to multi-source (GitHub + ClawdHub), added ClawdHub examples and shorthand syntax, expanded Supported Sources & Formats sections, updated troubleshooting guidance, added references to clawdhub-helper script.
Skill Documentation
.agent/tools/productivity/caldav-calendar-skill.md
.agent/services/hosting/proxmox-full-skill.md
New skill guide files: CalDAV provides vdirsyncer + khal workflow for calendar sync/management; Proxmox provides comprehensive REST API reference with endpoint categorization, setup instructions, token auth, and usage patterns for VE cluster/node/VM/container operations.

Sequence Diagram

sequenceDiagram
    participant User
    participant AddSkill as add-skill-helper.sh
    participant ClawdHub as clawdhub-helper.sh
    participant API as ClawdHub API
    participant Playwright
    participant Registry as skill-sources.json

    User->>AddSkill: add-skill clawdhub:slug
    AddSkill->>AddSkill: parse_url (detect ClawdHub)
    AddSkill->>ClawdHub: fetch slug --output /tmp
    ClawdHub->>API: resolve owner/slug
    API-->>ClawdHub: owner info
    ClawdHub->>Playwright: launch temp project + script
    Playwright->>Playwright: navigate to skill page
    Playwright->>Playwright: extract HTML → markdown
    Playwright-->>ClawdHub: SKILL.md content
    ClawdHub-->>AddSkill: SKILL.md file
    AddSkill->>AddSkill: convert to aidevops format
    AddSkill->>AddSkill: check for conflicts
    AddSkill->>Registry: register_skill (upstream: clawdhub)
    Registry-->>AddSkill: ack
    AddSkill-->>User: import complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Multiple new scripts with substantial logic density, API integration patterns, Playwright automation setup, bash error handling edge cases, and integration points across skill import workflow require careful review of security posture, CLI argument parsing, and shell script best practices.

Possibly related PRs

  • PR #170: Modifies .agent/configs/skill-sources.json by adding skill entries; directly overlaps with skill registry updates in this PR.
  • PR #135: Extends the add-skill system architecture and introduces script/registry patterns that this PR builds upon for ClawdHub support.
  • PR #154: Updates add-skill tooling, skill naming conventions, and conflict handling workflows that align with the ClawdHub-specific extensions here.

Poem

🌊 ClawdHub flows into aidevops streams,
Playwright draws skill.md from digital dreams,
Two new companions—Proxmox prowess, caldav care,
Shell scripts unite them with DevOps flair,
Zero debt maintained, A-grade prepared! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: adding ClawdHub as a new skill import source, which is the primary objective reflected across all modified and new files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 446 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Sat Jan 24 22:27:11 UTC 2026: Code review monitoring started
Sat Jan 24 22:27:11 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 446
Sat Jan 24 22:27:11 UTC 2026: Qlty - 0 issues found, auto-formatting applied
Sat Jan 24 22:27:13 UTC 2026: Codacy analysis completed with auto-fixes
Sat Jan 24 22:27:14 UTC 2026: Applied 1 automatic fixes

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 446
  • VULNERABILITIES: 0

Generated on: Sat Jan 24 22:28:36 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces ClawdHub as a new skill import source, which is a great addition. The implementation includes a new helper script, clawdhub-helper.sh, that cleverly uses Playwright for web scraping due to the lack of a public API for skill content. The main add-skill-helper.sh script is updated to integrate this new source. The changes are well-structured and the documentation has been updated accordingly. My review focuses on improving the robustness of the new helper script by making error handling more transparent and promoting consistency by using jq for JSON processing, which is already a project dependency.


# Install playwright and run the fetch script
log_info "Installing Playwright (temporary)..."
if (cd "$pw_dir" && npm install --silent 2>/dev/null && npx playwright install chromium --with-deps 2>/dev/null); then

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Suppressing all error output from npm install and npx playwright install with 2>/dev/null can make debugging very difficult if the installation fails. It's better to let error messages be displayed so that the user can understand and fix any issues with their Node.js environment, network, or permissions.

Suggested change
if (cd "$pw_dir" && npm install --silent 2>/dev/null && npx playwright install chromium --with-deps 2>/dev/null); then
if (cd "$pw_dir" && npm install --silent && npx playwright install chromium --with-deps); then

Comment on lines +792 to +795
display_name=$(echo "$api_response" | python3 -c "import sys,json; print(json.load(sys.stdin).get('skill',{}).get('displayName',''))" 2>/dev/null)
summary=$(echo "$api_response" | python3 -c "import sys,json; print(json.load(sys.stdin).get('skill',{}).get('summary',''))" 2>/dev/null)
owner_handle=$(echo "$api_response" | python3 -c "import sys,json; print(json.load(sys.stdin).get('owner',{}).get('handle',''))" 2>/dev/null)
version=$(echo "$api_response" | python3 -c "import sys,json; print(json.load(sys.stdin).get('latestVersion',{}).get('version',''))" 2>/dev/null)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These repeated calls to python3 to parse the JSON response are inefficient as each call starts a new Python interpreter. Since jq is a dependency of this project, you can use it to extract all required values. This is more performant and consistent with other parts of the codebase.

Suggested change
display_name=$(echo "$api_response" | python3 -c "import sys,json; print(json.load(sys.stdin).get('skill',{}).get('displayName',''))" 2>/dev/null)
summary=$(echo "$api_response" | python3 -c "import sys,json; print(json.load(sys.stdin).get('skill',{}).get('summary',''))" 2>/dev/null)
owner_handle=$(echo "$api_response" | python3 -c "import sys,json; print(json.load(sys.stdin).get('owner',{}).get('handle',''))" 2>/dev/null)
version=$(echo "$api_response" | python3 -c "import sys,json; print(json.load(sys.stdin).get('latestVersion',{}).get('version',''))" 2>/dev/null)
display_name=$(echo "$api_response" | jq -r '.skill.displayName // ""')
summary=$(echo "$api_response" | jq -r '.skill.summary // ""')
owner_handle=$(echo "$api_response" | jq -r '.owner.handle // ""')
version=$(echo "$api_response" | jq -r '.latestVersion.version // ""')

local response
response=$(curl -s --connect-timeout 10 --max-time 30 "${CLAWDHUB_API}/skills/${slug}")

if echo "$response" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

You're using python3 to validate the JSON response. Since jq is a project dependency, it would be more consistent and potentially more performant to use jq for this check. The -e flag in jq sets the exit code based on the result of the last filter, which is perfect for checks in if statements.

Suggested change
if echo "$response" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
if echo "$response" | jq -e . >/dev/null 2>&1; then

info=$(fetch_skill_info "$slug") || return 1

local owner
owner=$(echo "$info" | python3 -c "import sys,json; print(json.load(sys.stdin).get('owner',{}).get('handle',''))" 2>/dev/null)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency and performance, you can use jq here instead of python3 to parse the JSON and extract the owner's handle. This avoids spawning a separate Python process.

Suggested change
owner=$(echo "$info" | python3 -c "import sys,json; print(json.load(sys.stdin).get('owner',{}).get('handle',''))" 2>/dev/null)
owner=$(echo "$info" | jq -r '.owner.handle // ""')

Comment on lines +426 to +443
echo "$response" | python3 -c "
import sys, json
data = json.load(sys.stdin)
skill = data.get('skill', {})
owner = data.get('owner', {})
version = data.get('latestVersion', {})
stats = skill.get('stats', {})

print(f' Name: {skill.get(\"displayName\", \"?\")}')
print(f' Slug: {skill.get(\"slug\", \"?\")}')
print(f' Owner: @{owner.get(\"handle\", \"?\")}')
print(f' Version: {version.get(\"version\", \"?\")}')
print(f' Summary: {skill.get(\"summary\", \"\")}')
print(f' Stars: {stats.get(\"stars\", 0)}')
print(f' Downloads: {stats.get(\"downloads\", 0)}')
print(f' Installs: {stats.get(\"installsCurrent\", 0)}')
print()
"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This Python script for formatting the output can be replaced with a jq command. This would make the script more consistent by relying on a single tool for JSON processing and avoid the overhead of starting a Python interpreter.

Suggested change
echo "$response" | python3 -c "
import sys, json
data = json.load(sys.stdin)
skill = data.get('skill', {})
owner = data.get('owner', {})
version = data.get('latestVersion', {})
stats = skill.get('stats', {})
print(f' Name: {skill.get(\"displayName\", \"?\")}')
print(f' Slug: {skill.get(\"slug\", \"?\")}')
print(f' Owner: @{owner.get(\"handle\", \"?\")}')
print(f' Version: {version.get(\"version\", \"?\")}')
print(f' Summary: {skill.get(\"summary\", \"\")}')
print(f' Stars: {stats.get(\"stars\", 0)}')
print(f' Downloads: {stats.get(\"downloads\", 0)}')
print(f' Installs: {stats.get(\"installsCurrent\", 0)}')
print()
"
echo "$response" | jq -r '
. as $data |
" Name: \($data.skill.displayName // "?")",
" Slug: \($data.skill.slug // "?")",
" Owner: @\($data.owner.handle // "?")",
" Version: \($data.latestVersion.version // "?")",
" Summary: \($data.skill.summary // "")",
" Stars: \($data.skill.stats.stars // 0)",
" Downloads: \($data.skill.stats.downloads // 0)",
" Installs: \($data.skill.stats.installsCurrent // 0)",
""
'

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.agent/scripts/add-skill-helper.sh (2)

265-299: ClawdHub imports can land in the wrong category.

determine_target_path only scans file content, but in the ClawdHub flow it runs before fetching, so content is empty and keyword matching (calendar/proxmox/etc.) never triggers. Consider incorporating the summary/description into the scan.

♻️ Proposed fix
 determine_target_path() {
     local skill_name="$1"
     local description="$2"
     local source_dir="$3"
@@
-    local content=""
+    local content="${description}"
     if [[ -f "$source_dir/SKILL.md" ]]; then
-        content=$(cat "$source_dir/SKILL.md")
+        content="${content}"$'\n'"$(cat "$source_dir/SKILL.md")"
     elif [[ -f "$source_dir/AGENTS.md" ]]; then
-        content=$(cat "$source_dir/AGENTS.md")
+        content="${content}"$'\n'"$(cat "$source_dir/AGENTS.md")"
     fi

Also applies to: 807-810


969-979: Skip ClawdHub URLs in update checks to prevent noisy warnings.

The cmd_check_updates function calls parse_github_url on all skills, but ClawdHub URLs stored as https://clawdhub.com/{owner}/{slug} fail parsing and trigger a warning for every ClawdHub-sourced skill during update checks. Add a check to skip ClawdHub entries cleanly, deferring version checking support until it's implemented.

Suggested fix
     while IFS='|' read -r name url commit; do
+        if [[ "$url" == *clawdhub.com/* ]]; then
+            log_info "Skipping ClawdHub skill ($name) — update checks not yet supported"
+            continue
+        fi
         # Extract owner/repo from URL
         local parsed
🤖 Fix all issues with AI agents
In @.agent/scripts/add-skill-helper.sh:
- Around line 774-784: The ClawdHub `slug` is used to build filesystem paths
(e.g., `fetch_dir`) and must be strictly validated and sanitized before any path
operations or `rm -rf`; update the code that reads `slug` to (1) strip any URL
query/fragment and whitespace, (2) reject or normalize values containing path
separators or traversal tokens (like `../`, `/`, `\`, or leading `-`), and (3)
allow only a safe whitelist of characters (e.g., lowercase letters, digits,
hyphen, underscore, dot). Apply this validation where `slug` is used
(referencing the `slug` variable and the `fetch_dir` usage and the removal block
around lines 851-856) and fail early with an error if validation fails to
prevent directory escape.

In @.agent/scripts/clawdhub-helper.sh:
- Around line 335-346: The --output option parsing in the while loop can access
an unbound $2 (with set -u) when the argument is missing; update the case for
--output to validate that a next argument exists and is not another option
(e.g., check [[ -n "${2-}" && "${2:0:1}" != "-" ]]) before assigning to
output_dir, and if the check fails call log_error with a clear message and
return 1 instead of blindly using $2; adjust shifts only on success.
- Around line 379-381: Replace the current interpolation of $query into the
Python one-liner by passing the query as an argv parameter to Python to avoid
quoting issues: call python3 -c that uses sys.argv[1] and urllib.parse.quote to
produce the encoded value, and pass "$query" as the argument (preserving the
stderr redirect and the fallback to echo "$query"); update the assignment to
encoded_query so it uses this argv-based Python invocation and still falls back
to echo "$query" on error.
- Around line 304-317: The current fallback using find/head to locate SKILL.md
is non-deterministic; instead construct the deterministic path
"$output_dir/skills/$slug/SKILL.md" and check that file first (use that as
installed_skill), and only if that path doesn't exist fall back to the existing
find "$output_dir" -name "SKILL.md" -type f logic; update the block that sets
installed_skill (and the subsequent existence check that compares to
$output_file) to prefer the deterministic path and then fallback to the find
result so the script reliably copies the correct SKILL.md for the given $slug.
🧹 Nitpick comments (2)
.agent/scripts/clawdhub-helper.sh (2)

121-135: Surface HTTP/network failures before JSON validation.
curl -s can mask transport/HTTP errors; using -fS with explicit handling keeps A‑grade reliability.

♻️ Suggested change
-    response=$(curl -s --connect-timeout 10 --max-time 30 "${CLAWDHUB_API}/skills/${slug}")
+    response=$(curl -fsS --connect-timeout 10 --max-time 30 "${CLAWDHUB_API}/skills/${slug}") || {
+        log_error "Failed to fetch skill info (HTTP/network) for: $slug"
+        return 1
+    }
As per coding guidelines, improve error feedback for automation scripts.

162-168: Add a trap to guarantee temp directory cleanup.
Prevents orphaned temp dirs on early exits.

♻️ Suggested change
     pw_dir=$(mktemp -d "${TMPDIR:-/tmp}/clawdhub-pw-XXXXXX")
+    trap 'rm -rf "$pw_dir"' EXIT

Comment on lines +774 to +784
if [[ -z "$slug" ]]; then
log_error "ClawdHub slug required"
return 1
fi

log_info "Importing from ClawdHub: $slug"

# Get skill metadata from API
local api_response
api_response=$(curl -s --connect-timeout 10 --max-time 30 "${CLAWDHUB_API:-https://clawdhub.com/api/v1}/skills/${slug}" 2>/dev/null)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate ClawdHub slugs before using them in filesystem paths.

slug is user-controlled and is embedded in fetch_dir that gets removed with rm -rf. A crafted value like ../ can escape the temp directory. Add strict validation (and strip query/fragment) before any path usage to keep the automation A‑grade secure.

🔒 Proposed fix
 cmd_add_clawdhub() {
     local slug="$1"
@@
     if [[ -z "$slug" ]]; then
         log_error "ClawdHub slug required"
         return 1
     fi
+    # Strip query/fragment and validate to prevent path traversal
+    slug="${slug%%\?*}"
+    slug="${slug%%#*}"
+    if [[ ! "$slug" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,63}$ ]]; then
+        log_error "Invalid ClawdHub slug: $slug"
+        return 1
+    fi

Also applies to: 851-856

🤖 Prompt for AI Agents
In @.agent/scripts/add-skill-helper.sh around lines 774 - 784, The ClawdHub
`slug` is used to build filesystem paths (e.g., `fetch_dir`) and must be
strictly validated and sanitized before any path operations or `rm -rf`; update
the code that reads `slug` to (1) strip any URL query/fragment and whitespace,
(2) reject or normalize values containing path separators or traversal tokens
(like `../`, `/`, `\`, or leading `-`), and (3) allow only a safe whitelist of
characters (e.g., lowercase letters, digits, hyphen, underscore, dot). Apply
this validation where `slug` is used (referencing the `slug` variable and the
`fetch_dir` usage and the removal block around lines 851-856) and fail early
with an error if validation fails to prevent directory escape.

Comment on lines +304 to +317
# Fallback: try clawdhub CLI
if command -v npx &>/dev/null; then
log_info "Trying: npx clawdhub install $slug"
if (cd "$output_dir" && npx --yes clawdhub@latest install "$slug" --force 2>/dev/null); then
# clawdhub installs to ./skills/<slug>/SKILL.md
local installed_skill
installed_skill=$(find "$output_dir" -name "SKILL.md" -type f 2>/dev/null | head -1)
if [[ -n "$installed_skill" && -f "$installed_skill" ]]; then
if [[ "$installed_skill" != "$output_file" ]]; then
cp "$installed_skill" "$output_file"
fi
log_success "Fetched via clawdhub CLI"
return 0
fi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the actual file and its context
cat -n .agent/scripts/clawdhub-helper.sh | sed -n '295,325p'

Repository: marcusquinn/aidevops

Length of output: 1435


🏁 Script executed:

# Search for any documentation or comments about clawdhub output structure
rg -i "clawdhub" --type sh -A 5 -B 5 .agent/scripts/

Repository: marcusquinn/aidevops

Length of output: 27391


🌐 Web query:

clawdhub CLI install output directory structure where does npx clawdhub install write SKILL.md

💡 Result:

npx clawdhub install <skill-slug> installs a skill as a folder containing SKILL.md at:

  • Default (no flags): ./skills/<skill-slug>/SKILL.md (relative to your current working directory) [1]
  • If a Clawdbot workspace is configured: it may instead use the workspace root as the workdir, installing to <workspace>/skills/<skill-slug>/SKILL.md (unless you override) [1][2]

You can change where it writes by setting:

  • --workdir <dir> (or CLAWDHUB_WORKDIR) to change the base directory
  • --dir <dir> to change the skills subdirectory name (default is skills) [1]

Sources: [1] [2]


Use deterministic path when clawdhub CLI copies SKILL.md.

When npx clawdhub install is run in $output_dir, it writes to ./skills/<slug>/SKILL.md relative to the working directory. The current find ... | head -1 approach is non-deterministic and will grab any SKILL.md in the output directory if multiple skills exist, potentially copying the wrong file. Use the expected path directly with a fallback:

Suggested fix
-            installed_skill=$(find "$output_dir" -name "SKILL.md" -type f 2>/dev/null | head -1)
+            installed_skill="${output_dir}/skills/${slug}/SKILL.md"
+            if [[ ! -f "$installed_skill" ]]; then
+                installed_skill=$(find "$output_dir" -name "SKILL.md" -type f -path "*/${slug}/*" 2>/dev/null | head -1)
+            fi
📝 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.

Suggested change
# Fallback: try clawdhub CLI
if command -v npx &>/dev/null; then
log_info "Trying: npx clawdhub install $slug"
if (cd "$output_dir" && npx --yes clawdhub@latest install "$slug" --force 2>/dev/null); then
# clawdhub installs to ./skills/<slug>/SKILL.md
local installed_skill
installed_skill=$(find "$output_dir" -name "SKILL.md" -type f 2>/dev/null | head -1)
if [[ -n "$installed_skill" && -f "$installed_skill" ]]; then
if [[ "$installed_skill" != "$output_file" ]]; then
cp "$installed_skill" "$output_file"
fi
log_success "Fetched via clawdhub CLI"
return 0
fi
# Fallback: try clawdhub CLI
if command -v npx &>/dev/null; then
log_info "Trying: npx clawdhub install $slug"
if (cd "$output_dir" && npx --yes clawdhub@latest install "$slug" --force 2>/dev/null); then
# clawdhub installs to ./skills/<slug>/SKILL.md
local installed_skill
installed_skill="${output_dir}/skills/${slug}/SKILL.md"
if [[ ! -f "$installed_skill" ]]; then
installed_skill=$(find "$output_dir" -name "SKILL.md" -type f -path "*/${slug}/*" 2>/dev/null | head -1)
fi
if [[ -n "$installed_skill" && -f "$installed_skill" ]]; then
if [[ "$installed_skill" != "$output_file" ]]; then
cp "$installed_skill" "$output_file"
fi
log_success "Fetched via clawdhub CLI"
return 0
fi
🤖 Prompt for AI Agents
In @.agent/scripts/clawdhub-helper.sh around lines 304 - 317, The current
fallback using find/head to locate SKILL.md is non-deterministic; instead
construct the deterministic path "$output_dir/skills/$slug/SKILL.md" and check
that file first (use that as installed_skill), and only if that path doesn't
exist fall back to the existing find "$output_dir" -name "SKILL.md" -type f
logic; update the block that sets installed_skill (and the subsequent existence
check that compares to $output_file) to prefer the deterministic path and then
fallback to the find result so the script reliably copies the correct SKILL.md
for the given $slug.

Comment on lines +335 to +346
# Parse options
while [[ $# -gt 0 ]]; do
case "$1" in
--output)
output_dir="$2"
shift 2
;;
*)
log_error "Unknown option: $1"
return 1
;;
esac
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard --output against a missing argument.
With set -u, an empty value triggers an unbound variable error.

🐛 Suggested fix
             --output)
-                output_dir="$2"
-                shift 2
+                if [[ $# -lt 2 || -z "${2:-}" ]]; then
+                    log_error "--output requires a directory path"
+                    return 1
+                fi
+                output_dir="$2"
+                shift 2
                 ;;
As per coding guidelines, fail fast with clear CLI errors.
📝 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.

Suggested change
# Parse options
while [[ $# -gt 0 ]]; do
case "$1" in
--output)
output_dir="$2"
shift 2
;;
*)
log_error "Unknown option: $1"
return 1
;;
esac
# Parse options
while [[ $# -gt 0 ]]; do
case "$1" in
--output)
if [[ $# -lt 2 || -z "${2:-}" ]]; then
log_error "--output requires a directory path"
return 1
fi
output_dir="$2"
shift 2
;;
*)
log_error "Unknown option: $1"
return 1
;;
esac
🤖 Prompt for AI Agents
In @.agent/scripts/clawdhub-helper.sh around lines 335 - 346, The --output
option parsing in the while loop can access an unbound $2 (with set -u) when the
argument is missing; update the case for --output to validate that a next
argument exists and is not another option (e.g., check [[ -n "${2-}" &&
"${2:0:1}" != "-" ]]) before assigning to output_dir, and if the check fails
call log_error with a clear message and return 1 instead of blindly using $2;
adjust shifts only on success.

Comment on lines +379 to +381
local encoded_query
encoded_query=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$query'))" 2>/dev/null || echo "$query")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid interpolating the search query into Python code.
Single quotes break the command; argv-based encoding is safer.

🐛 Suggested fix
-    encoded_query=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$query'))" 2>/dev/null || echo "$query")
+    encoded_query=$(python3 -c 'import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))' "$query" 2>/dev/null || echo "$query")
📝 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.

Suggested change
local encoded_query
encoded_query=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$query'))" 2>/dev/null || echo "$query")
local encoded_query
encoded_query=$(python3 -c 'import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))' "$query" 2>/dev/null || echo "$query")
🤖 Prompt for AI Agents
In @.agent/scripts/clawdhub-helper.sh around lines 379 - 381, Replace the
current interpolation of $query into the Python one-liner by passing the query
as an argv parameter to Python to avoid quoting issues: call python3 -c that
uses sys.argv[1] and urllib.parse.quote to produce the encoded value, and pass
"$query" as the argument (preserving the stderr redirect and the fallback to
echo "$query"); update the assignment to encoded_query so it uses this
argv-based Python invocation and still falls back to echo "$query" on error.

@marcusquinn marcusquinn merged commit ab7de61 into main Jan 24, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant