diff --git a/assets/README.md b/assets/README.md index 2ff4076..9dcb6f1 100644 --- a/assets/README.md +++ b/assets/README.md @@ -1,10 +1,10 @@ # AI Assets -One script installs everything. No manual copy/paste. No cloning required. +One script installs everything. No cloning or manual copy/paste required. ## Quick Start -### Option 1 — You Cloned This Repo +### Cloned Repo ```bash # Install everything for iOS @@ -16,7 +16,7 @@ One script installs everything. No manual copy/paste. No cloning required. ./assets/setup.sh instructions ``` -### Option 2 — No Clone (Run Remotely) +### No Clone (Run Remotely) Point the script at the repo and run. Nothing to download first. @@ -41,20 +41,19 @@ That's it. | Command | What It Does | |---------|-------------| -| `setup.sh skills [platform]` | Install skills from a curated YAML list | -| `setup.sh agents` | Install all agent prompt files | -| `setup.sh instructions` | Install all instruction rule files | +| `setup.sh skills [platform]` | Install skills from a curated list | +| `setup.sh agents` | Install all agent prompt files (auto-discovered) | +| `setup.sh instructions` | Install all instruction rule files (auto-discovered) | | `setup.sh all [platform]` | All of the above in one shot | -| `setup.sh update-lists` | Regenerate manifests from directory contents (local only) | | `setup.sh help` | Print usage | ### Platforms (for skills) | Platform | List File | |----------|-----------| -| `ios` | ios-skills.yaml | -| `android` | android-skills.yaml | -| `shared` (default) | shared-skills.yaml | +| `ios` | ios-skills.txt | +| `android` | android-skills.txt | +| `shared` (default) | shared-skills.txt | ## Where Things Get Installed @@ -66,26 +65,26 @@ That's it. ## Adding New Assets -When you add a new agent or instruction file to `assets/agents/` or `assets/instructions/`, run: +- **Agents or instructions** — Drop the file into `assets/agents/` or `assets/instructions/` and push. The script discovers files from the directory automatically. No manifest to update. +- **Skills** — Add the install command to the appropriate `.txt` file (e.g., `ios-skills.txt`). One command per line. -```bash -./assets/setup.sh update-lists -``` +## How It Works -This regenerates `agents.list` and `instructions.list` so remote installs pick up the new files. Commit the updated lists. +| Mode | Agents / Instructions | Skills | +|------|----------------------|--------| +| **Local** (cloned repo) | `find` scans the directory | Reads the `.txt` file | +| **Remote** (no clone) | Queries GitLab/GitHub API to list files | Downloads the `.txt` file | ## Folder Structure ``` assets/ setup.sh ← the installer - agents.list ← auto-generated manifest - instructions.list ← auto-generated manifest - ios-skills.yaml ← curated iOS skills - android-skills.yaml ← curated Android skills - shared-skills.yaml ← curated cross-platform skills - agents/ ← agent prompt files - instructions/ ← instruction rule files + ios-skills.txt ← curated iOS skills (one per line) + android-skills.txt ← curated Android skills + shared-skills.txt ← curated cross-platform skills + agents/ ← agent prompt files (auto-discovered) + instructions/ ← instruction rule files (auto-discovered) ``` ## Environment Variables @@ -94,4 +93,5 @@ assets/ |----------|---------|-----------| | `ASSETS_BASE_URL` | Base URL for remote downloads | Only without a clone | | `AGENTS_DIR` | Custom agents install path | No | -| `INSTRUCTIONS_DIR` | Custom instructions install path | No | \ No newline at end of file +| `INSTRUCTIONS_DIR` | Custom instructions install path | No | +| `REPO_TOKEN` | Auth token for private repos | Only if API rejects | \ No newline at end of file diff --git a/assets/agents.list b/assets/agents.list deleted file mode 100644 index 497a176..0000000 --- a/assets/agents.list +++ /dev/null @@ -1,10 +0,0 @@ -arch.agent.md -debug.agent.md -principal-software-engineer.agent.md -prompt-builder.agent.md -prompt-engineer.agent.md -software-engineer-agent-v1.agent.md -task-planner.agent.md -task-researcher.agent.md -tech-debt-remediation-plan.agent.md -thinking-beast-mode.agent.md diff --git a/assets/android-skills.txt b/assets/android-skills.txt new file mode 100644 index 0000000..37acc7e --- /dev/null +++ b/assets/android-skills.txt @@ -0,0 +1,2 @@ +# Android Skills +# Each line is passed to: npx skills add diff --git a/assets/android-skills.yaml b/assets/android-skills.yaml deleted file mode 100644 index 53fce10..0000000 --- a/assets/android-skills.yaml +++ /dev/null @@ -1,2 +0,0 @@ -version: 1 -skills: [] \ No newline at end of file diff --git a/assets/instructions.list b/assets/instructions.list deleted file mode 100644 index 431b076..0000000 --- a/assets/instructions.list +++ /dev/null @@ -1 +0,0 @@ -swift.instructions.md diff --git a/assets/ios-skills.txt b/assets/ios-skills.txt new file mode 100644 index 0000000..e10d862 --- /dev/null +++ b/assets/ios-skills.txt @@ -0,0 +1,4 @@ +# iOS Skills +# Each line is passed to: npx skills add + +https://github.com/avdlee/swiftui-agent-skill --skill swiftui-expert-skill diff --git a/assets/ios-skills.yaml b/assets/ios-skills.yaml deleted file mode 100644 index 129000d..0000000 --- a/assets/ios-skills.yaml +++ /dev/null @@ -1,4 +0,0 @@ -version: 1 -skills: - - name: swiftui-expert-skill - install: "https://github.com/avdlee/swiftui-agent-skill --skill swiftui-expert-skill" \ No newline at end of file diff --git a/assets/setup.sh b/assets/setup.sh index aa5ebe2..fbf3770 100755 --- a/assets/setup.sh +++ b/assets/setup.sh @@ -2,22 +2,24 @@ set -euo pipefail # ───────────────────────────────────────────────────────────────────── -# Mobile AI Assets Installer +# Mobile AI Assets Installer v2.0.0 # # One script to install skills, agents, and instructions. -# Works both locally (cloned repo) and remotely (curl, no clone). +# Agents and instructions are discovered automatically from directories. +# Skills are read from simple text files (one install entry per line). # -# Local: ./assets/setup.sh agents -# Remote: export ASSETS_BASE_URL="https://..." -# bash <(curl -fsSL "$ASSETS_BASE_URL/setup.sh") agents +# Local: ./assets/setup.sh all ios +# Remote: export ASSETS_BASE_URL="https://gitlab.com//repo/-/raw/develop/assets" +# bash <(curl -fsSL "$ASSETS_BASE_URL/setup.sh") all ios # ───────────────────────────────────────────────────────────────────── -VERSION="1.0.0" +VERSION="2.0.0" # ── Configuration (override with env vars) ─────────────────────────── ASSETS_BASE_URL="${ASSETS_BASE_URL:-}" AGENTS_DIR="${AGENTS_DIR:-$HOME/.copilot/agents}" INSTRUCTIONS_DIR="${INSTRUCTIONS_DIR:-./instructions}" +REPO_TOKEN="${REPO_TOKEN:-}" # ── Colors ─────────────────────────────────────────────────────────── RED='\033[0;31m' @@ -35,7 +37,6 @@ fail() { printf "${RED}✗${NC} %s\n" "$*" >&2; exit 1; } heading() { printf "\n${BOLD}── %s ──${NC}\n" "$*"; } # ── Mode Detection ─────────────────────────────────────────────────── -# Detect if running from a cloned repo or remotely via curl. MODE="" ASSETS_DIR="" @@ -43,113 +44,186 @@ detect_mode() { local script_dir script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)" || script_dir="" - if [[ -n "$script_dir" && -f "$script_dir/agents.list" ]]; then + if [[ -n "$script_dir" && -d "$script_dir/agents" ]]; then MODE="local" ASSETS_DIR="$script_dir" elif [[ -n "$ASSETS_BASE_URL" ]]; then MODE="remote" else - fail "Set ASSETS_BASE_URL to run without a clone. Example: - export ASSETS_BASE_URL=\"https://gitlab.com/org/repo/-/raw/main/assets\" + fail "Set ASSETS_BASE_URL to run without a clone. + export ASSETS_BASE_URL=\"https://gitlab.com/org/repo/-/raw/develop/assets\" bash <(curl -fsSL \"\$ASSETS_BASE_URL/setup.sh\") agents" fi } +# ── Remote File Discovery ─────────────────────────────────────────── +# Build the hosting platform API URL from the raw-file base URL. +# Supports GitLab and GitHub URL formats. +derive_api_url() { + local subdir="$1" + + # GitLab: https://gitlab.com//-/raw// + if [[ "$ASSETS_BASE_URL" =~ ^(https?://[^/]+)/(.+)/-/raw/([^/]+)/(.+)$ ]]; then + local host="${BASH_REMATCH[1]}" + local project="${BASH_REMATCH[2]}" + local branch="${BASH_REMATCH[3]}" + local base_path="${BASH_REMATCH[4]}" + local encoded + encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$project', safe=''))" 2>/dev/null) || return 1 + echo "${host}/api/v4/projects/${encoded}/repository/tree?path=${base_path}/${subdir}&ref=${branch}&per_page=100" + return 0 + fi + + # GitHub: https://raw.githubusercontent.com//// + if [[ "$ASSETS_BASE_URL" =~ ^https://raw\.githubusercontent\.com/([^/]+/[^/]+)/([^/]+)/(.+)$ ]]; then + local owner_repo="${BASH_REMATCH[1]}" + local branch="${BASH_REMATCH[2]}" + local base_path="${BASH_REMATCH[3]}" + echo "https://api.github.com/repos/${owner_repo}/contents/${base_path}/${subdir}?ref=${branch}" + return 0 + fi + + return 1 +} + +# Query the hosting API to list files in a remote directory. +list_remote_files() { + local subdir="$1" ext="$2" + + local api_url + api_url=$(derive_api_url "$subdir") || fail "Cannot parse ASSETS_BASE_URL into an API URL. + Supported formats: + GitLab: https://gitlab.com//-/raw//assets + GitHub: https://raw.githubusercontent.com////assets" + + local auth_header="" + [[ -n "$REPO_TOKEN" ]] && auth_header="Authorization: Bearer $REPO_TOKEN" + + local response + response=$(curl -fsSL ${auth_header:+-H "$auth_header"} "$api_url" 2>/dev/null) \ + || fail "API request failed. If the repo is private, set REPO_TOKEN. + export REPO_TOKEN=\"glpat-...\" # GitLab personal access token + export REPO_TOKEN=\"ghp_...\" # GitHub personal access token" + + # Extract filenames from JSON. Works for both GitLab and GitHub responses. + local files + files=$(echo "$response" \ + | grep -o '"name":"[^"]*"' \ + | sed 's/"name":"//;s/"//' \ + | grep "${ext}\$" || true) + + echo "$files" +} + # ── Fetch / Download ──────────────────────────────────────────────── -# Read a file from the local clone or download it from the remote URL. fetch() { local path="$1" if [[ "$MODE" == "local" ]]; then cat "$ASSETS_DIR/$path" else - curl -fsSL "$ASSETS_BASE_URL/$path" + curl -fsSL ${REPO_TOKEN:+-H "Authorization: Bearer $REPO_TOKEN"} "$ASSETS_BASE_URL/$path" fi } -# Download a file to a destination path. download_to() { local src="$1" dest="$2" mkdir -p "$(dirname "$dest")" if [[ "$MODE" == "local" ]]; then cp "$ASSETS_DIR/$src" "$dest" else - curl -fsSL "$ASSETS_BASE_URL/$src" -o "$dest" + curl -fsSL ${REPO_TOKEN:+-H "Authorization: Bearer $REPO_TOKEN"} "$ASSETS_BASE_URL/$src" -o "$dest" fi } # ── Commands ───────────────────────────────────────────────────────── -# -- skills [platform] ------------------------------------------------ +# -- skills [platform] ──────────────────────────────────────────────── +# Reads a plain-text skills file. Each non-empty, non-comment line is +# passed to `npx skills add`. cmd_skills() { local platform="${1:-shared}" - local manifest="${platform}-skills.yaml" + local manifest="${platform}-skills.txt" heading "Skills ($platform)" - local yaml - yaml="$(fetch "$manifest")" || fail "Could not fetch $manifest" + local content + content="$(fetch "$manifest")" || fail "Could not fetch $manifest" - local installs - installs=$(echo "$yaml" | sed -n 's/^[[:space:]]*install:[[:space:]]*//p' | sed 's/^"//;s/"$//') + local count=0 + while IFS= read -r line; do + [[ -z "$line" || "$line" == \#* ]] && continue + info "npx skills add $line" + read -r -a args <<< "$line" + npx skills add "${args[@]}" + count=$((count + 1)) + done <<< "$content" - if [[ -z "$installs" ]]; then - warn "No install entries in $manifest — nothing to do." + if [[ $count -eq 0 ]]; then + warn "No entries in $manifest — nothing to install." + else + ok "$count skill(s) installed. Restart your editor if they don't appear." + fi +} + +# -- agents ─────────────────────────────────────────────────────────── +# Discovers .agent.md files automatically (local: find, remote: API). +cmd_agents() { + heading "Agents → $AGENTS_DIR" + mkdir -p "$AGENTS_DIR" + + local files count=0 + + if [[ "$MODE" == "local" ]]; then + files=$(find "$ASSETS_DIR/agents" -maxdepth 1 -type f -name '*.agent.md' -exec basename {} \; | sort) + else + files=$(list_remote_files "agents" ".agent.md") + fi + + if [[ -z "$files" ]]; then + warn "No agent files found." return 0 fi - local count=0 - while IFS= read -r entry; do - [[ -z "$entry" ]] && continue - read -r -a args <<< "$entry" - info "npx skills add ${args[*]}" - npx skills add "${args[@]}" - count=$((count + 1)) - done <<< "$installs" - - ok "$count skill(s) installed. Restart your editor if they don't appear." -} - -# -- agents ------------------------------------------------------------ -cmd_agents() { - heading "Agents → $AGENTS_DIR" - - local list - list="$(fetch "agents.list")" || fail "Could not fetch agents.list" - - mkdir -p "$AGENTS_DIR" - - local count=0 while IFS= read -r file; do - [[ -z "$file" || "$file" == \#* ]] && continue + [[ -z "$file" ]] && continue info "$file" download_to "agents/$file" "$AGENTS_DIR/$file" count=$((count + 1)) - done <<< "$list" + done <<< "$files" ok "$count agent(s) installed." } -# -- instructions ------------------------------------------------------ +# -- instructions ───────────────────────────────────────────────────── +# Discovers .instructions.md files automatically. cmd_instructions() { heading "Instructions → $INSTRUCTIONS_DIR" - - local list - list="$(fetch "instructions.list")" || fail "Could not fetch instructions.list" - mkdir -p "$INSTRUCTIONS_DIR" - local count=0 + local files count=0 + + if [[ "$MODE" == "local" ]]; then + files=$(find "$ASSETS_DIR/instructions" -maxdepth 1 -type f -name '*.instructions.md' -exec basename {} \; | sort) + else + files=$(list_remote_files "instructions" ".instructions.md") + fi + + if [[ -z "$files" ]]; then + warn "No instruction files found." + return 0 + fi + while IFS= read -r file; do - [[ -z "$file" || "$file" == \#* ]] && continue + [[ -z "$file" ]] && continue info "$file" download_to "instructions/$file" "$INSTRUCTIONS_DIR/$file" count=$((count + 1)) - done <<< "$list" + done <<< "$files" ok "$count instruction(s) installed." } -# -- all [platform] ---------------------------------------------------- +# -- all [platform] ─────────────────────────────────────────────────── cmd_all() { local platform="${1:-shared}" cmd_skills "$platform" @@ -159,39 +233,7 @@ cmd_all() { ok "All done." } -# -- update-lists (local only) ----------------------------------------- -# Regenerate agents.list and instructions.list from directory contents. -cmd_update_lists() { - if [[ "$MODE" != "local" ]]; then - fail "update-lists only works from a cloned repo." - fi - - heading "Updating manifests" - - # agents.list - local agents_dir="$ASSETS_DIR/agents" - if [[ -d "$agents_dir" ]]; then - find "$agents_dir" -maxdepth 1 -type f -name '*.agent.md' \ - | xargs -I{} basename {} \ - | sort > "$ASSETS_DIR/agents.list" - ok "agents.list → $(wc -l < "$ASSETS_DIR/agents.list" | tr -d ' ') file(s)" - else - warn "No agents/ directory found — skipping." - fi - - # instructions.list - local instr_dir="$ASSETS_DIR/instructions" - if [[ -d "$instr_dir" ]]; then - find "$instr_dir" -maxdepth 1 -type f -name '*.instructions.md' \ - | xargs -I{} basename {} \ - | sort > "$ASSETS_DIR/instructions.list" - ok "instructions.list → $(wc -l < "$ASSETS_DIR/instructions.list" | tr -d ' ') file(s)" - else - warn "No instructions/ directory found — skipping." - fi -} - -# -- help -------------------------------------------------------------- +# -- help ───────────────────────────────────────────────────────────── cmd_help() { cat < + +vercel-labs/agent-skills diff --git a/assets/shared-skills.yaml b/assets/shared-skills.yaml deleted file mode 100644 index a3ded81..0000000 --- a/assets/shared-skills.yaml +++ /dev/null @@ -1,4 +0,0 @@ -version: 1 -skills: - - name: onboarding-cro - install: "vercel-labs/agent-skills" \ No newline at end of file diff --git a/docs/ai/skills.md b/docs/ai/skills.md index 107b660..6b1ccbe 100644 --- a/docs/ai/skills.md +++ b/docs/ai/skills.md @@ -46,27 +46,20 @@ Keep the approved list in a single repo and organize by platform. This repo alre ```text /assets/ - setup.sh ← single installer script - agents.list ← auto-generated manifest - instructions.list ← auto-generated manifest - ios-skills.yaml - android-skills.yaml - shared-skills.yaml - agents/ - instructions/ - README.md + setup.sh ← the installer (auto-discovers agents & instructions) + ios-skills.txt ← curated iOS skills (one per line) + android-skills.txt ← curated Android skills + shared-skills.txt ← curated cross-platform skills + agents/ ← agent prompt files (auto-discovered) + instructions/ ← instruction rule files (auto-discovered) ``` ### Project Manifest Example -Use a small manifest to declare the approved skills and versions for the project: +Skills files are plain text. Each non-empty, non-comment line is passed to `npx skills add`: -```yaml -version: 1 -skills: - - name: swiftui-expert-skill - install: "https://github.com/avdlee/swiftui-agent-skill --skill swiftui-expert-skill" - - name: onboarding-cro - install: "vercel-labs/agent-skills" +```text +# iOS Skills +https://github.com/avdlee/swiftui-agent-skill --skill swiftui-expert-skill ``` ### Sync Workflow @@ -97,18 +90,17 @@ We use a single, approved path: update the curated list under assets/ and run th - CLI reference: https://skills.sh/docs/cli ### How It Works (High-Level) -1. You update assets/shared-skills.yaml (or a platform list) with approved install entries. +1. You update `assets/shared-skills.txt` (or a platform list) with approved install entries — one per line. 2. Run `./assets/setup.sh skills` (or `skills ios`, `skills android`). -3. The script uses `npx skills` to install each skill. +3. The script uses `npx skills add` to install each entry. ### Install The CLI (No Global Install Required) The CLI runs via `npx`, so you do not need a global install. ### Example Install Entry -```yaml -skills: - - name: swiftui-expert-skill - install: "https://github.com/avdlee/swiftui-agent-skill --skill swiftui-expert-skill" +Add one line to the platform file (e.g., `ios-skills.txt`): +```text +https://github.com/avdlee/swiftui-agent-skill --skill swiftui-expert-skill ``` ### Notes @@ -170,14 +162,14 @@ Use swiftui-expert-skill to review this view for best practices. Then use webapp ``` ## Central Assets Repo (Recommended) -Do not list every skill in this guide. Instead, point readers to a single repo that contains the approved agents, skills, and instructions they can download. +Do not list every skill in this guide. Instead, point readers to the assets folder (or a future dedicated repo) that contains the approved agents, skills, and instructions. -### What To Include In The Assets Repo -- Agents (agent prompt files) -- Skills (SKILL files and any required setup) -- Instructions (repo-level and editor-level guidance) +Everything is installable with one command: +```bash +./assets/setup.sh all ios +``` - A short README with install and update steps -- Curated skill lists (for example, ios-skills.yaml, android-skills.yaml) +- Curated skill lists (for example, ios-skills.txt, android-skills.txt) ### Link To The Assets Repo - Repo: (link)