Files
stack/packages/skills/brain/brain.sh
Jason Woltje 10b66ddb4a feat(#23): implement mosaic-plugin-brain skill
- Add brain skill for Ideas/Brain API integration
- Quick capture for brain dumps
- Semantic search and query capabilities
- Full CRUD operations on ideas
- Tag management and filtering
- Shell script CLI with colored output
- Comprehensive documentation (SKILL.md, README.md)
- Configuration via env vars or ~/.config/mosaic/brain.conf
2026-01-29 21:14:17 -06:00

397 lines
8.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Mosaic Brain CLI - Interface to Mosaic Stack Ideas/Brain API
# Usage: brain.sh <command> [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
)
if [[ -n "$data" ]]; then
args+=(-d "$data")
fi
curl "${args[@]}" "$url"
}
# 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
local payload="{\"content\": \"$content\""
[[ -n "$title" ]] && payload+=", \"title\": \"$title\""
[[ -n "$category" ]] && payload+=", \"category\": \"$category\""
[[ -n "$status" ]] && payload+=", \"status\": \"$status\""
[[ -n "$priority" ]] && payload+=", \"priority\": \"$priority\""
if [[ -n "$tags" ]]; then
local tags_json
tags_json=$(echo "$tags" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$";""))')
payload+=", \"tags\": $tags_json"
fi
payload+="}"
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
local payload="{"
local first=true
if [[ -n "$title" ]]; then
payload+="\"title\": \"$title\""
first=false
fi
if [[ -n "$content" ]]; then
[[ "$first" == false ]] && payload+=", "
payload+="\"content\": \"$content\""
first=false
fi
if [[ -n "$status" ]]; then
[[ "$first" == false ]] && payload+=", "
payload+="\"status\": \"$status\""
first=false
fi
if [[ -n "$category" ]]; then
[[ "$first" == false ]] && payload+=", "
payload+="\"category\": \"$category\""
first=false
fi
if [[ -n "$tags" ]]; then
[[ "$first" == false ]] && payload+=", "
local tags_json
tags_json=$(echo "$tags" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$";""))')
payload+="\"tags\": $tags_json"
first=false
fi
payload+="}"
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 "$@"