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
This commit is contained in:
396
packages/skills/brain/brain.sh
Executable file
396
packages/skills/brain/brain.sh
Executable file
@@ -0,0 +1,396 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user