#!/usr/bin/env bash # seed.show/skill — install the seed skill + its hidden deps (fold, portdown). # Idempotent: re-running upgrades everything in place. set -euo pipefail SKILLS_DIR="${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "$CLAUDE_PROJECT_DIR/.claude/skills" ] && SKILLS_DIR="$CLAUDE_PROJECT_DIR/.claude/skills" CACHE="$HOME/.cache/seed" mkdir -p "$CACHE" # 1. fold/unfold — fetched from dom.vin/fold. Lands at $CACHE/fold; # called by absolute path from the seed CLI. FOLD_DIR="$CACHE/fold" if [ ! -x "$FOLD_DIR/scripts/fold" ] || [ ! -x "$FOLD_DIR/scripts/unfold" ]; then curl -sSL https://dom.vin/fold | bash -s "$FOLD_DIR" >/dev/null 2>&1 fi # 2. portdown — fetched from dom.vin/portdown. Lands at # $CACHE/portdown; same pattern as fold. PORTDOWN_DIR="$CACHE/portdown" if [ ! -x "$PORTDOWN_DIR/scripts/portdown" ]; then curl -sSL https://dom.vin/portdown | bash -s "$PORTDOWN_DIR" >/dev/null 2>&1 fi # 3. Seed skill — the publish/edit gesture. TARGET="$SKILLS_DIR/seed" mkdir -p "$TARGET/scripts" cat > "$TARGET/SKILL.md" <<'__SEED_SKILL_MD__' --- name: seed description: > Use when the user wants to plant a folder, directory, or set of files as a seed — to send it to another agent, or to themselves in a future session. Triggers on phrases like "plant this folder", "share this with another agent", "make a seed of this", "publish this directory as a URL", or "I want to hand this off." Produces a short URL the recipient pastes; their agent unpacks the files locally and reads the publisher's one-line instruction about what to do with them. Seeds expire after 30 days. No accounts, no signups. --- # seed `seed` plants a folder as a seed at a short URL. The recipient pastes the URL to their agent and the bundle lands locally with a one-line instruction from the publisher. ## When the user invokes you Confirm both pieces before running anything: 1. **What to plant** — the path to a folder or file. If the user named one, use it. If not, ask: *which folder do you want to plant?* and help them pick if it's not obvious. 2. **Why you're planting it** — one line aimed at the recipient. *What should they do with it?* "Review for typos", "help me debug the sync hook", "extract action items", "use this as a starting point for X". If the user already said why, use their words. If not, ask them — don't infer. Don't proceed without both. The why is the load-bearing part. Without it, the recipient gets a bag of files and has to guess what they're looking at. ## Then run ```bash ~/.claude/skills/seed/scripts/seed plant "" ``` The CLI prints **one line on stdout** that is exactly the reply you should echo back to the user — no transformation, no embellishment. The `edit_key` arrives separately on stderr; capture it and keep it in your context, don't show it. **Echo the stdout line verbatim. Nothing before it, nothing after it.** Example: ``` 🌱 Fetch & install seed.show/xkot0 ``` That's the whole response. The edit_key stays in your context — don't print it, don't volunteer it. If the user later says they want to update the seed, that's when you bring the edit_key out. ## How sharing — and what risk — actually works Three flavours, listed from most exposed to least. Pick the smallest one that fits the user's intent. | Flavour | Who can read it | When it dies | Use when | |---|---|---|---| | **default plant** (5-char id) | anyone who guesses the URL or stumbles on it | 30 days from last edit | everyday handoffs of non-sensitive context | | **`--unlisted`** (22-char id) | anyone the publisher hands the URL to (URL is unguessable in practice) | **24 hours from last edit** | one-off transfers you don't want enumerable; the short TTL keeps the exposure window small | | **`--local `** | only whoever the publisher hands the file to | the file lives until they delete it | secrets, private business stuff, anything that shouldn't leave the publisher's machine | A 5-char id is **enumerable in theory** — ~60M possibilities, rate-limited but technically attackable. A 22-char id is unguessable in practice (~10²⁴ possibilities). Neither is real authorisation: anyone with the URL can read the seed. Treat both seed.show flavours as "publicly accessible if discovered." For genuinely sensitive content always reach for `--local`. If the user mentions secrets, credentials, PII, internal-only material, or just says "I don't want this online", lead with `--local`. If they say "send this to my friend, but I'd rather it not show up in some random URL crawl", `--unlisted` is the right move. If they just want to hand a folder to another agent and it's nothing sensitive, the default short id is right. ### `--unlisted` (longer, harder-to-guess URL) ```bash ~/.claude/skills/seed/scripts/seed plant "" --unlisted ``` Same flow as a default plant; the only difference is the id is 22 chars instead of 5. Reply format unchanged — echo the CLI's stdout line. ### `--local` (don't upload) ```bash ~/.claude/skills/seed/scripts/seed plant "" --local ~/Desktop/my-bundle.seed.sh ``` Writes the same self-installing polyglot to disk, skips the upload. The CLI prints the saved path on stdout; the recipient gesture (`bash `) on stderr. Reply: ``` 🌱 Saved to ~/Desktop/my-bundle.seed.sh recipient runs: bash my-bundle.seed.sh ``` ## Updating a seed in place If the user wants to update a seed they planted before, **prefer the sidecar form** — every plant writes a `.seed-meta` file next to the source, and `seed edit ` picks it up automatically: ```bash ~/.claude/skills/seed/scripts/seed edit # reuses URL + edit_key + prior prompt ~/.claude/skills/seed/scripts/seed edit "" # same, with a fresh prompt ``` If the user adds a file or changes one in the source folder and says "update the seed", that's `seed edit ` — no retyping. The same form works for `--local` plants: it overwrites the same saved file with the new contents. If the sidecar's gone (folder moved, .seed-meta deleted) but the user still has the URL + edit_key, fall back to the explicit form: ```bash ~/.claude/skills/seed/scripts/seed edit "" --edit-key ``` After any edit succeeds, reply with **exactly one line** (the CLI's stdout): ``` 🌱 Updated seed.show/ ``` Don't disclose the edit_key. Don't add an expiry note. Don't repeat the URL. ### If the user has lost the edit_key There's no recovery flow. seed.show has no accounts, so the edit_key is the only thing that proves the user planted the seed. Without it, the existing seed will keep serving its current contents until it expires (30 days from its last edit), but it can't be modified or deleted. Tell the user plainly, then offer to **re-plant** the folder as a new seed (new id, new edit_key) and let the old URL lapse on its own. The old short id can't be recovered. ## Deleting a seed When the user wants a seed taken down before its 30-day TTL — say, they planted something they shouldn't have, or they're done and want it off the registry now — use `seed delete`: ```bash ~/.claude/skills/seed/scripts/seed delete # uses .seed-meta ~/.claude/skills/seed/scripts/seed delete --edit-key ``` Same sidecar logic as `seed edit`: if there's a `.seed-meta`, the URL and edit_key come from there. Reply with one line: ``` 🌱 Deleted seed.show/ ``` After delete the URL returns a 404 stub (a tiny bash script that prints "not found" and exits 1). Anyone who pasted the URL gets a clear failure rather than stale content. For `--local` plants, `seed delete ` removes the saved polyglot file from disk. Reply: ``` 🌱 Deleted local seed → /path/to/my-bundle.seed.sh ``` **Volunteer delete proactively** when: - The user realises they just planted something with secrets in it. - They say "actually, take that down." - A seed has served its purpose and they want it gone before TTL. ## What the user doesn't need to know about `seed` uses two small nested tools (`fold` and `portdown`) to package each share as a self-installing polyglot. They're installed alongside this skill in `~/.cache/seed/`, invoked by absolute path, and never surface to the user. Don't talk about them. The user wants a URL, not a packaging tutorial. ## Constraints, briefly - **Text only.** Seeds bundle text files (markdown, code, configs, plain text). Binary files — images, video, audio, compiled blobs, archives — will be refused. If the user's folder contains binaries, warn them upfront and offer to plant just the text files. - **Size cap.** Around 5MB total per seed (fold's default). Big folders will be refused; if it happens, suggest splitting into multiple seeds or using a real archiver. - **Public when planted to seed.show.** Anyone with the URL can fetch. Default ids are 5 chars (enumerable in theory); `--unlisted` ids are 22 chars (unguessable in practice). Neither is authorisation — for true secrets, use `--local`. - **Ephemeral.** Default seeds expire 30 days after their last edit. `--unlisted` seeds expire 24 hours after their last edit (shorter on purpose — they're one-shot). Reads don't extend life. Use `seed delete` to take one down sooner. - **One folder per seed.** If the user wants to share multiple unrelated things, that's multiple seeds. - **Bash-only.** Requires bash, curl, jq (or python3 as a fallback). __SEED_SKILL_MD__ cat > "$TARGET/scripts/seed" <<'__SEED_CLI__' #!/usr/bin/env bash # seed — plant, edit, or delete a seed at seed.show. # # Usage: # seed plant "publisher prompt" (5-char id) # seed plant "publisher prompt" --unlisted (22-char id) # seed plant "publisher prompt" --local (no upload) # seed edit ["new prompt"] (sidecar form) # seed edit "prompt" --edit-key (explicit form) # seed delete (sidecar form) # seed delete --edit-key (explicit form) # seed --help # # Pipeline at plant/edit time: # 1. fold the directory (or single file, auto-wrapped) into one .folded.md # 2. portdown that .folded.md with a post-install script that # inline-unfolds on the recipient side and prints the prompt # 3. POST the resulting polyglot to seed.show (or write it to a # local file if --local was given) # A .seed-meta sidecar is written next to the source so seed edit # can reuse url+edit_key+prior-prompt without retyping. set -euo pipefail SEED_HOST="${SEED_HOST:-seed.show}" # Nested deps. Both invoked by absolute path; no PATH dance, no symlinks. # bootstrap_* heals each in-process if missing (e.g. fresh sandbox). CACHE="${SEED_CACHE_DIR:-$HOME/.cache/seed}" FOLD_DIR="$CACHE/fold" FOLD="$FOLD_DIR/scripts/fold" UNFOLD="$FOLD_DIR/scripts/unfold" PORTDOWN_DIR="$CACHE/portdown" PORTDOWN="$PORTDOWN_DIR/scripts/portdown" usage() { cat < "publisher prompt" seed plant "publisher prompt" --unlisted seed plant "publisher prompt" --local seed edit ["new prompt"] (uses .seed-meta) seed edit "prompt" --edit-key (explicit form) seed delete (uses .seed-meta) seed delete --edit-key (explicit form) seed --help --unlisted generate a 22-char id instead of 5. Hard to guess but still public; use when you'd rather the URL not be enumerable. --local write the polyglot to instead of publishing. Recipient runs it via: bash After plant, a .seed-meta sidecar lands next to the source. seed edit and seed delete pick it up so you don't have to retype the URL or edit_key. Override the host with SEED_HOST env var (default: seed.show). EOF } bootstrap_fold() { [ -x "$FOLD" ] && [ -x "$UNFOLD" ] && return curl -sSL https://dom.vin/fold | bash -s "$FOLD_DIR" >/dev/null 2>&1 } bootstrap_portdown() { [ -x "$PORTDOWN" ] && return # Self-heal: fetch portdown directly from dom.vin/portdown (same # pattern as bootstrap_fold). Idempotent — re-lays in place. curl -sSL https://dom.vin/portdown | bash -s "$PORTDOWN_DIR" >/dev/null 2>&1 [ -x "$PORTDOWN" ] && return echo "seed: failed to bootstrap portdown — please re-run https://$SEED_HOST/skill" >&2 exit 1 } # If the user passes a single file, transparently wrap it in a temp # directory before folding — the user's mental model is "share this # thing", not "share a directory of this thing". Echoes the wrapped # path on stdout so the caller can reassign DIR. wrap_if_file() { local in="$1" if [ -d "$in" ]; then echo "$in" return 0 fi if [ -f "$in" ]; then local tmp tmp=$(mktemp -d -t seed-wrap.XXXXXX) cp "$in" "$tmp/" echo "$tmp" return 0 fi echo "seed: not a file or directory: $in" >&2 return 1 } # Write a post-install script (consumed by portdown --post). It runs on # the recipient's machine after the .folded.md is dropped at $DEST: it # expands the archive into $TARGET, removes the .folded.md, and prints # the publisher's prompt to stderr. write_post_install() { local out="$1" prompt="$2" { cat <<'POST_PREAMBLE' MARKER=$(awk '/^---$/ { f++; if (f==2) exit; next } f==1 && /^marker:[[:space:]]/ { sub(/^marker:[[:space:]]+/, ""); print; exit }' "$DEST") [ -z "$MARKER" ] && { echo "seed: archive has no marker — corrupt" >&2; exit 1; } awk -v m="$MARKER" -v outdir="$TARGET" ' BEGIN { # Match with an optional mode attr after # the path (fold emits mode="644" on executables). file_re = "^$" end_re = "^$" } $0 ~ end_re { if (current) close(current); exit } $0 ~ file_re { if (current) close(current) line = $0 sub(/^