#!/usr/bin/env bash set -euo pipefail # Mosaic Brain CLI - Interface to Mosaic Stack Ideas/Brain API # Usage: brain.sh [options] # Load configuration CONFIG_FILE="${HOME}/.config/mosaic/brain.conf" if [[ -f "$CONFIG_FILE" ]]; then # shellcheck source=/dev/null source "$CONFIG_FILE" fi # Configuration with defaults API_URL="${MOSAIC_API_URL:-http://localhost:3001}" WORKSPACE_ID="${MOSAIC_WORKSPACE_ID:-}" API_TOKEN="${MOSAIC_API_TOKEN:-}" # Color output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Helper functions error() { echo -e "${RED}Error: $1${NC}" >&2 exit 1 } success() { echo -e "${GREEN}$1${NC}" } warn() { echo -e "${YELLOW}$1${NC}" } check_config() { [[ -z "$WORKSPACE_ID" ]] && error "MOSAIC_WORKSPACE_ID not set" [[ -z "$API_TOKEN" ]] && error "MOSAIC_API_TOKEN not set" } # API call helper api_call() { local method="$1" local endpoint="$2" local data="${3:-}" local url="${API_URL}${endpoint}" local args=( -X "$method" -H "Authorization: Bearer ${API_TOKEN}" -H "Content-Type: application/json" -H "x-workspace-id: ${WORKSPACE_ID}" -s -w "\n%{http_code}" ) if [[ -n "$data" ]]; then args+=(-d "$data") fi local response response=$(curl "${args[@]}" "$url") # Extract HTTP status code from last line local http_code http_code=$(echo "$response" | tail -n1) local body body=$(echo "$response" | sed '$d') # Check for errors if [[ "$http_code" -ge 400 ]]; then local error_msg error_msg=$(echo "$body" | jq -r '.message // .error // "API request failed"' 2>/dev/null || echo "API request failed") error "HTTP $http_code: $error_msg" fi echo "$body" } # Commands cmd_capture() { local content="" local title="" while [[ $# -gt 0 ]]; do case $1 in --title) title="$2" shift 2 ;; *) content="$1" shift ;; esac done [[ -z "$content" ]] && error "Content required for capture" local payload payload=$(jq -n \ --arg content "$content" \ --arg title "$title" \ '{content: $content} + (if $title != "" then {title: $title} else {} end)') response=$(api_call POST "/api/ideas/capture" "$payload") echo "$response" | jq -r '.id // empty' > /dev/null || error "Failed to capture idea" local idea_id idea_id=$(echo "$response" | jq -r '.id') success "✓ Idea captured: $idea_id" echo "$response" | jq '.' } cmd_create() { local content="" local title="" local tags="" local category="" local status="" local priority="" while [[ $# -gt 0 ]]; do case $1 in --content) content="$2" shift 2 ;; --title) title="$2" shift 2 ;; --tags) tags="$2" shift 2 ;; --category) category="$2" shift 2 ;; --status) status="$2" shift 2 ;; --priority) priority="$2" shift 2 ;; *) content="$1" shift ;; esac done [[ -z "$content" ]] && error "Content required" # Build JSON payload using jq for safety local payload payload=$(jq -n \ --arg content "$content" \ --arg title "$title" \ --arg category "$category" \ --arg status "$status" \ --arg priority "$priority" \ --arg tags "$tags" \ '{content: $content} + (if $title != "" then {title: $title} else {} end) + (if $category != "" then {category: $category} else {} end) + (if $status != "" then {status: $status} else {} end) + (if $priority != "" then {priority: $priority} else {} end) + (if $tags != "" then {tags: ($tags | split(",") | map(gsub("^\\s+|\\s+$";"")))} else {} end)' ) response=$(api_call POST "/api/ideas" "$payload") echo "$response" | jq -r '.id // empty' > /dev/null || error "Failed to create idea" local idea_id idea_id=$(echo "$response" | jq -r '.id') success "✓ Idea created: $idea_id" echo "$response" | jq '.' } cmd_list() { local limit="20" local tags="" local status="" while [[ $# -gt 0 ]]; do case $1 in --limit) limit="$2" shift 2 ;; --tags) tags="$2" shift 2 ;; --status) status="$2" shift 2 ;; *) shift ;; esac done local query="?limit=$limit" [[ -n "$tags" ]] && query+="&tags=$tags" [[ -n "$status" ]] && query+="&status=$status" response=$(api_call GET "/api/ideas${query}") echo "$response" | jq '.' } cmd_get() { local idea_id="$1" [[ -z "$idea_id" ]] && error "Idea ID required" response=$(api_call GET "/api/ideas/${idea_id}") echo "$response" | jq '.' } cmd_update() { local idea_id="" local title="" local content="" local tags="" local add_tags="" local remove_tags="" local status="" local category="" idea_id="$1" shift [[ -z "$idea_id" ]] && error "Idea ID required" while [[ $# -gt 0 ]]; do case $1 in --title) title="$2" shift 2 ;; --content) content="$2" shift 2 ;; --tags) tags="$2" shift 2 ;; --add-tags) add_tags="$2" shift 2 ;; --remove-tags) remove_tags="$2" shift 2 ;; --status) status="$2" shift 2 ;; --category) category="$2" shift 2 ;; *) shift ;; esac done # Build update payload using jq for safety local payload payload=$(jq -n \ --arg title "$title" \ --arg content "$content" \ --arg status "$status" \ --arg category "$category" \ --arg tags "$tags" \ '(if $title != "" then {title: $title} else {} end) + (if $content != "" then {content: $content} else {} end) + (if $status != "" then {status: $status} else {} end) + (if $category != "" then {category: $category} else {} end) + (if $tags != "" then {tags: ($tags | split(",") | map(gsub("^\\s+|\\s+$";"")))} else {} end)' ) response=$(api_call PATCH "/api/ideas/${idea_id}" "$payload") echo "$response" | jq -r '.id // empty' > /dev/null || error "Failed to update idea" success "✓ Idea updated: $idea_id" echo "$response" | jq '.' } cmd_delete() { local idea_id="$1" [[ -z "$idea_id" ]] && error "Idea ID required" response=$(api_call DELETE "/api/ideas/${idea_id}") success "✓ Idea deleted: $idea_id" } cmd_query() { local query="$*" [[ -z "$query" ]] && error "Query text required" local payload payload=$(jq -n --arg query "$query" '{query: $query}') response=$(api_call POST "/api/brain/query" "$payload") echo "$response" | jq '.' } cmd_search() { local search_term="" local limit="20" while [[ $# -gt 0 ]]; do case $1 in --limit) limit="$2" shift 2 ;; *) search_term="$1" shift ;; esac done [[ -z "$search_term" ]] && error "Search term required" local query="?q=$(echo "$search_term" | jq -sRr @uri)&limit=$limit" response=$(api_call GET "/api/brain/search${query}") echo "$response" | jq '.' } cmd_tags() { # Get all ideas and extract unique tags response=$(api_call GET "/api/ideas?limit=1000") echo "$response" | jq -r '.data[]?.tags[]? // empty' | sort -u } # Main main() { [[ $# -eq 0 ]] && error "Command required. Use: capture, create, list, get, update, delete, query, search, tags" check_config local command="$1" shift case "$command" in capture) cmd_capture "$@" ;; create) cmd_create "$@" ;; list) cmd_list "$@" ;; get) cmd_get "$@" ;; update) cmd_update "$@" ;; delete) cmd_delete "$@" ;; query) cmd_query "$@" ;; search) cmd_search "$@" ;; tags) cmd_tags "$@" ;; *) error "Unknown command: $command" ;; esac } main "$@"