diff --git a/CLI_STANDARD.md b/CLI_STANDARD.md new file mode 100644 index 0000000..1ddb330 --- /dev/null +++ b/CLI_STANDARD.md @@ -0,0 +1,210 @@ +# CLI Integration Standard + +Standard for adding programmatic CLI access to all projects so AI agents can work without browser automation. + +## Rule: CLI Tools Live INSIDE the Project + +**Location:** `{project-root}/scripts/` + +**Why:** +- Version controlled with the project +- Self-documenting (API + CLI in same repo) +- Portable (clone repo, CLI works immediately) +- Project-specific logic stays with project +- No hunting around workspace folders + +## Required File Structure + +``` +project-root/ +├── scripts/ +│ ├── crud.sh # Main CRUD operations +│ ├── attach-file.sh # File attachments +│ ├── README.md # Usage documentation +│ └── .env.example # Required environment variables +├── api/ # REST API routes (used by CLI) +│ └── items/ +│ ├── route.ts +│ └── [id]/ +│ ├── route.ts +│ └── attachments/ +│ └── route.ts +└── README.md # Project README with CLI section +``` + +## Required CLI Commands + +Every project MUST implement these: + +```bash +# From project root: +./scripts/crud.sh list [filters] # List all items +./scripts/crud.sh get # Get single item +./scripts/crud.sh create # Create item +./scripts/crud.sh update # Update field +./scripts/crud.sh delete # Delete item +./scripts/attach-file.sh # Attach file to item +``` + +## Environment Variables + +Each project's CLI needs these in `scripts/.env` (gitignored): + +```bash +# Supabase direct access (for bypassing auth) +SUPABASE_URL=https://xxxx.supabase.co +SUPABASE_SERVICE_ROLE_KEY=eyJ... + +# Or API-based access +API_URL=http://localhost:3000/api +API_KEY=secret_key_for_cli_access +``` + +## Supabase Direct Pattern (Recommended) + +For Supabase projects, use direct DB access with service role key: + +**File:** `scripts/crud.sh` +```bash +#!/bin/bash +set -e + +# Load env from scripts/.env +source "$(dirname "$0")/.env" + +action=$1 +shift + +case $action in + list) + curl -s "${SUPABASE_URL}/rest/v1/items?select=*" \ + -H "apikey: ${SUPABASE_SERVICE_ROLE_KEY}" \ + -H "Authorization: Bearer ${SUPABASE_SERVICE_ROLE_KEY}" | jq '.' + ;; + get) + id=$1 + curl -s "${SUPABASE_URL}/rest/v1/items?id=eq.${id}" \ + -H "apikey: ${SUPABASE_SERVICE_ROLE_KEY}" \ + -H "Authorization: Bearer ${SUPABASE_SERVICE_ROLE_KEY}" | jq '.[0]' + ;; + create) + data=$1 + curl -s -X POST "${SUPABASE_URL}/rest/v1/items" \ + -H "apikey: ${SUPABASE_SERVICE_ROLE_KEY}" \ + -H "Authorization: Bearer ${SUPABASE_SERVICE_ROLE_KEY}" \ + -H "Content-Type: application/json" \ + -d "$data" | jq '.' + ;; + update) + id=$1 + field=$2 + value=$3 + curl -s -X PATCH "${SUPABASE_URL}/rest/v1/items?id=eq.${id}" \ + -H "apikey: ${SUPABASE_SERVICE_ROLE_KEY}" \ + -H "Authorization: Bearer ${SUPABASE_SERVICE_ROLE_KEY}" \ + -H "Content-Type: application/json" \ + -d "{\"${field}\": \"${value}\"}" | jq '.' + ;; + delete) + id=$1 + curl -s -X DELETE "${SUPABASE_URL}/rest/v1/items?id=eq.${id}" \ + -H "apikey: ${SUPABASE_SERVICE_ROLE_KEY}" \ + -H "Authorization: Bearer ${SUPABASE_SERVICE_ROLE_KEY}" + echo "Deleted ${id}" + ;; + *) + echo "Usage: $0 {list|get |create |update |delete }" + exit 1 + ;; +esac +``` + +## File Attachment Pattern + +**File:** `scripts/attach-file.sh` +```bash +#!/bin/bash +set -e + +source "$(dirname "$0")/.env" + +ITEM_ID=$1 +FILE_PATH=$2 + +if [ -z "$ITEM_ID" ] || [ -z "$FILE_PATH" ]; then + echo "Usage: $0 " + exit 1 +fi + +if [ ! -f "$FILE_PATH" ]; then + echo "Error: File not found: $FILE_PATH" + exit 1 +fi + +# Read and encode file +mime=$(file -b --mime-type "$FILE_PATH") +base64=$(base64 -i "$FILE_PATH") +filename=$(basename "$FILE_PATH") +size=$(stat -f%z "$FILE_PATH" 2>/dev/null || stat -c%s "$FILE_PATH") + +# Build attachment JSON +attachment=$(jq -n \ + --arg id "$(uuidgen | tr '[:upper:]' '[:lower:]')" \ + --arg name "$filename" \ + --arg type "$mime" \ + --arg data "data:${mime};base64,${base64}" \ + --argjson size "$size" \ + '{id: $id, name: $name, type: $type, dataUrl: $data, size: $size}') + +# Get current attachments, append new one +current=$(curl -s "${SUPABASE_URL}/rest/v1/items?id=eq.${ITEM_ID}&select=attachments" \ + -H "apikey: ${SUPABASE_SERVICE_ROLE_KEY}" \ + -H "Authorization: Bearer ${SUPABASE_SERVICE_ROLE_KEY}" | jq '.[0].attachments // []') + +updated=$(echo "$current" | jq ". + [$attachment]") + +# Update item +curl -s -X PATCH "${SUPABASE_URL}/rest/v1/items?id=eq.${ITEM_ID}" \ + -H "apikey: ${SUPABASE_SERVICE_ROLE_KEY}" \ + -H "Authorization: Bearer ${SUPABASE_SERVICE_ROLE_KEY}" \ + -H "Content-Type: application/json" \ + -d "{\"attachments\": $updated}" | jq '.' + +echo "✅ Attached ${filename} (${size} bytes) to item ${ITEM_ID}" +``` + +## Per-Project Reference + +### Gantt Board +- **Location:** `/Users/mattbruce/Documents/Projects/OpenClaw/Web/gantt-board/scripts/` +- **Scripts:** `gantt-task-crud.sh`, `attach-file.sh`, `view-attachment.sh` +- **Auth:** Supabase service role key +- **Table:** `public.tasks` + +### Adding CLI to a New Project + +1. Create `scripts/` directory in project root +2. Copy template scripts from gantt-board +3. Update table names and fields for your project +4. Create `scripts/.env` with Supabase credentials +5. Test: `./scripts/crud.sh list` +6. Commit to git: `git add scripts/ && git commit -m "Add CLI tools for AI access"` +7. Update project's README.md with CLI usage section + +## Agent Instructions + +When working on a project: +1. Check for `scripts/` directory in project root +2. If CLI exists, use it: `./scripts/crud.sh list` +3. If CLI doesn't exist, create it using patterns above +4. NEVER create files in workspace scripts folder for project-specific tools + +## Verification Checklist + +Before saying a task is done: +- [ ] CLI tools are in `{project}/scripts/`, NOT workspace +- [ ] Scripts are executable (`chmod +x`) +- [ ] `.env` file exists with credentials (gitignored) +- [ ] Scripts work: `./scripts/crud.sh list` returns data +- [ ] Changes are committed to git +- [ ] Project README documents CLI usage