DAO-native community collaboration skill for the Shibarmy Librarian.
When Doggy DAO proposals pass, they contain GitHub repo URLs pointing to real projects that need contributors. This OpenClaw skill lets any Shibarmy member's local Librarian agent discover those projects, claim tasks, coordinate with the pack, and ship PRs — all driven by DAO votes with zero central authority.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Doggy DAO │────▶│ GitHub Repo │────▶│ Your Fork │
│ (Snapshot) │ │ (Lead's) │ │ (Your work) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
│ dao-watch.sh │ project-join.sh │ git push + PR
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ ntfy.sh community channel │
│ channel-post.sh / channel-read.sh │
└─────────────────────────────────────────────────────────┘
- DAO votes — A proposal passes on Doggy DAO with a GitHub repo URL embedded in the proposal body
- Discover —
dao-watch.shqueries the Snapshot.org GraphQL API for theshibainu.ethspace, finds closed proposals that passed, and extracts GitHub URLs from their body text - Join —
project-join.shlists open Issues on the project repo (via GitHub API) and posts a claim comment when you pick a task - Build — Fork the repo, create a branch, write code, open a PR — standard open-source workflow
- Communicate —
channel-post.shandchannel-read.shkeep the pack in sync via ntfy.sh (zero-auth pub/sub) - Dashboard —
collab-status.shcombines all of the above into one view
| Requirement | Why | Required by |
|---|---|---|
POSIX shell (sh) |
All scripts are POSIX-compliant, no bash-isms | everything |
curl |
HTTP requests to Snapshot API, GitHub API, ntfy.sh | everything |
git |
Fork/branch/PR workflow | project-join.sh |
awk, sed |
JSON parsing (no jq dependency) | dao-watch.sh, project-join.sh |
GITHUB_TOKEN env var |
GitHub personal access token for Issue comments | project-join.sh (claim only) |
What does NOT require a token: Listing DAO proposals, listing open Issues (public repos), reading/writing to the ntfy.sh channel, viewing the dashboard. The token is only needed when you want to comment on an Issue to claim a task.
# Drop into your skills directory
cp -r shibarmy-collab/ ~/.openclaw/skills/shibarmy-collab/
# Or symlink for development
ln -s "$(pwd)/shibarmy-collab" ~/.openclaw/skills/shibarmy-collabThe Librarian auto-detects the skill via SKILL.md frontmatter. The name field must match the directory name (shibarmy-collab).
Every script works independently from the command line:
# See what the DAO has approved
./scripts/dao-watch.sh --list
# Preview without network calls
./scripts/dao-watch.sh --dry-run
# Join a project (token needed to claim, not to browse)
export GITHUB_TOKEN="ghp_your_token_here"
./scripts/project-join.sh --repo https://github.com/org/project --member yourname
# Post a progress update
./scripts/channel-post.sh --type update --project myproject --member yourname \
--message "Finished the auth module, PR incoming"
# Read recent channel activity
./scripts/channel-read.sh --since 6h
# Full dashboard
./scripts/collab-status.shWhat it does: Finds Doggy DAO proposals that passed and contain a GitHub repo URL.
Data source strategy (ordered by reliability):
-
Primary: Snapshot.org GraphQL API — Queries
hub.snapshot.org/graphqlfor theshibainu.ethspace. Returns structured JSON with proposal titles, bodies, vote scores, and state. This is reliable because Snapshot is the actual voting platform. -
Fallback: HTML scrape of shib.io — If Snapshot is unreachable, falls back to fetching the DAO page HTML. Important limitation: shib.io is likely a JavaScript SPA (React/Next.js), which means
curlwill only get the empty HTML shell, not the rendered proposal content. This fallback exists for resilience but may return zero results on SPA pages.
Caching: Results are cached to /tmp/shibarmy-dao-cache.txt with a 30-minute TTL. If both live sources fail, a stale cache is used as a last resort. The cache file includes a SOURCE: tag so the parser knows which format to expect.
Parsing approach: The Snapshot JSON is parsed with awk (no jq dependency required). For each closed proposal, it extracts the title, ID (truncated to 8 chars for display), and scans the body text for github.com/owner/repo URLs. Only proposals with a GitHub URL are shown.
dao-watch.sh --list # All passed proposals with repo URLs
dao-watch.sh --latest # Most recently passed only
dao-watch.sh --dry-run # Show sources and cache status
dao-watch.sh --help # Full usageEnvironment overrides:
| Variable | Default | Purpose |
|---|---|---|
SHIBARMY_DAO_URL |
shib.io DAO page | Override HTML fallback URL |
SHIBARMY_SNAPSHOT_SPACE |
shibainu.eth |
Override Snapshot space ID |
What it does: Lists open Issues on a GitHub repo, lets you claim one by posting a comment, and notifies the ntfy.sh channel.
How GitHub API calls work:
- GET requests (list/search issues): Work without
GITHUB_TOKENon public repos. You get 60 requests/hour unauthenticated vs 5,000 authenticated. - POST requests (claim comment): Require
GITHUB_TOKEN. The script checks for the token before attempting writes and gives a clear error if missing.
JSON parsing: GitHub API returns nested JSON (issues contain user objects, label arrays, etc.). The parser uses tr ',' '\n' to put each field on its own line, then sed extracts only top-level "number" and "title" fields. A stateful awk script pairs each number with its following title, skipping nested duplicates.
Input sanitization: The --member handle is sanitized before being embedded in the JSON comment body — quotes, backslashes, and control characters are stripped to prevent JSON injection.
project-join.sh --repo <url> # List open issues
project-join.sh --repo <url> --task "title" # Claim a specific task
project-join.sh --repo <url> --task "title" --member alice
project-join.sh --repo <url> --dry-run # Preview actionsWhat it does: Posts a tagged message to the ntfy.sh topic with appropriate priority.
ntfy.sh API usage:
POST https://ntfy.sh/{topic}
Headers:
Title: Shibarmy [{TYPE}] {project}
Priority: low|default|high
Tags: dog,memo (comma-separated emoji shortcodes)
Body: [{TYPE}] @{member} on {project}: {message}
Priority mapping:
| --type | ntfy.sh priority | Numeric | Behavior |
|---|---|---|---|
update |
low |
2 | Normal delivery, no sound on mobile |
help |
default |
3 | Normal notification |
blocker |
high |
4 | Highlighted, sound on mobile |
channel-post.sh --type update --project myrepo --message "PR ready" --member alice
channel-post.sh --type blocker --project myrepo --message "CI broken"
channel-post.sh --type help --project myrepo --message "Need reviewer"
channel-post.sh --dry-run --type update --project x --message yWhat it does: Polls ntfy.sh for recent messages and displays them with priority indicators.
ntfy.sh polling API:
GET https://ntfy.sh/{topic}/json?poll=1&since={duration}
The poll=1 parameter means "return immediately with available messages" (no long-polling). Each message is a single JSON line with fields: event, message, title, time (Unix timestamp), priority (1-5).
Output format:
!! 2026-04-06 14:30 [Title] message (priority 4-5: blocker/urgent)
-- 2026-04-06 13:15 [Title] message (priority 3: normal)
.. 2026-04-06 12:00 [Title] message (priority 1-2: low/update)
Timestamp handling: Uses date -d @timestamp on Linux, falls back to date -r timestamp on macOS. Both are tested in the script.
Filtering: The --filter flag matches [TYPE] tags in message bodies (e.g., [BLOCKER], [HELP], [UPDATE], [JOIN]). This is a simple string match, not a structured query.
channel-read.sh # Last 1 hour, all types
channel-read.sh --since 6h # Last 6 hours
channel-read.sh --since 24h --filter blocker # Blockers in last day
channel-read.sh --dry-run # Show what would be fetchedWhat it does: Runs dao-watch.sh --list, channel-read.sh --since 6h, and filtered channel reads for help/blocker messages. Combines the output into a formatted dashboard.
How it works internally:
- Resolves its own directory with
$(cd "$(dirname "$0")" && pwd)so it can find sibling scripts regardless of where you run it from - Checks that each script exists and is executable before calling
- Suppresses stderr from child scripts (you can run them individually for debug output)
- Shows the ntfy.sh topic name and current timestamp in the footer
collab-status.sh # Full dashboard
collab-status.sh --dry-run # Show what commands would runThis skill uses ntfy.sh as a public, no-auth message bus.
- Default topic:
shibarmy-librarian - Override: Set the
SHIBARMY_TOPICenvironment variable - No accounts needed — anyone can read or post
- Subscribe in browser: Visit
https://ntfy.sh/shibarmy-librarian - Subscribe on phone: Install the ntfy app (Android/iOS) and add the topic
- Subscribe via curl:
curl -s ntfy.sh/shibarmy-librarian/json?poll=1&since=1h
| Feature | ntfy.sh | Slack/Discord | Telegram |
|---|---|---|---|
| Auth required to read | No | Yes | Yes |
| Auth required to post | No | Yes (webhook/bot) | Yes (bot) |
| Account needed | No | Yes | Yes |
| curl-friendly | Yes | Partial | Partial |
| Mobile push | Yes (via app) | Yes | Yes |
| Self-hostable | Yes | No | No |
| Free tier limits | 250 msgs/day | N/A | N/A |
The tradeoff: no message persistence guarantees and no threading. For coordination messages (status updates, help requests), this is fine — they're ephemeral by nature.
All messages follow this pattern for reliable filtering:
[TYPE] @member on project: actual message text
Where TYPE is one of: JOIN, UPDATE, BLOCKER, HELP
| Variable | Required | Default | Used by |
|---|---|---|---|
GITHUB_TOKEN |
Only for claiming tasks | (none) | project-join.sh |
SHIBARMY_TOPIC |
No | shibarmy-librarian |
all channel scripts |
SHIBARMY_DAO_URL |
No | shib.io DAO page | dao-watch.sh |
SHIBARMY_SNAPSHOT_SPACE |
No | shibainu.eth |
dao-watch.sh |
-
Snapshot space ID: The default space is
shibainu.eth. If Doggy DAO moves to a different Snapshot space or a different voting platform entirely, you'll need to updateSHIBARMY_SNAPSHOT_SPACEor modify the script. -
HTML fallback is unreliable: The shib.io fallback scraper will likely return nothing if the page is rendered client-side (SPA). It exists as a best-effort fallback, not a reliable source. Snapshot API is the real workhorse.
-
JSON parsing without jq: We parse GitHub API and Snapshot JSON using
sed/awkto avoid requiringjq. This works for the specific fields we need (issue numbers, titles, proposal bodies) but would break on exotic edge cases like titles containing escaped quotes. If you havejqavailable, a future version could detect and prefer it. -
ntfy.sh is public: Anyone who knows the topic name can read and post. This is a feature (zero-barrier coordination) and a limitation (no access control). For sensitive projects, use a randomized topic name via
SHIBARMY_TOPIC. -
No quorum verification:
dao-watch.shtreats all closed Snapshot proposals as "passed." It doesn't verify quorum thresholds or check if the winning choice was "For" vs "Against." A proper implementation would inspectscoresandchoicesarrays. This is noted as a future improvement. -
Rate limits without token: GitHub allows 60 unauthenticated API requests per hour per IP. If you're browsing issues frequently without a token, you may hit this. Set
GITHUB_TOKENto get 5,000/hour.
shibarmy-collab/
├── SKILL.md # OpenClaw skill definition (AgentSkills.io spec)
├── README.md # This file
├── LICENSE # MIT License
├── scripts/
│ ├── dao-watch.sh # Snapshot API → passed proposals with GitHub URLs
│ ├── project-join.sh # GitHub Issues → claim task → notify channel
│ ├── channel-post.sh # ntfy.sh POST with typed priority
│ ├── channel-read.sh # ntfy.sh GET with filtering
│ └── collab-status.sh # Dashboard combining all of the above
├── templates/
│ ├── update.md # Progress update template
│ ├── blocker.md # Blocker report template
│ └── help-wanted.md # Help request template
├── .learnings/
│ └── LEARNINGS.md # Architecture decisions and evolution notes
└── examples/
└── sample-session.md # Full walkthrough: DAO vote → join → code → PR
All scripts use #!/bin/sh and avoid:
[[ ]](use[ ]instead)- Arrays (use positional parameters or pipe chains)
localkeyword (use subshells or unique variable names)- Process substitution
<()(use pipes or temp files) IGNORECASEin awk (usetolower()for case-insensitive matching)source(use.if needed)
Tested patterns: stat -c %Y (Linux) with stat -f %m (macOS) fallback for file timestamps. date -d @ts (Linux) with date -r ts (macOS) fallback for epoch conversion.
- The DAO is the source of truth. No config files, no job codes, no coordinator accounts. If a proposal passed and has a GitHub URL, that's a live project.
- Open-source norms. Issues, forks, PRs — the workflow every developer already knows.
- No gatekeepers. ntfy.sh needs no auth. Public GitHub repos are browsable without tokens. The barrier to entry is
curlandgit. - POSIX portable. Runs on macOS, Linux, Alpine, CI runners, WSL — anything with a POSIX shell.
- Honest about limitations. Every script has
--dry-run. The README tells you what won't work and why.
This skill itself is a Doggy DAO project. Check the Issues tab, claim a task, and send a PR. Use the skill to contribute to the skill.
MIT — see LICENSE for details.