288 lines
8.6 KiB
Bash
288 lines
8.6 KiB
Bash
#!/bin/bash
|
|
# mission-control-docs library - Full CRUD for Mission Control documents
|
|
# Location: ~/.agents/skills/mission-control-docs/lib/docs.sh
|
|
# Refactored to use API endpoints with machine token auth
|
|
|
|
# Configuration
|
|
MC_API_URL="${MC_API_URL:-https://mission-control.twisteddevices.com/api}"
|
|
MC_MACHINE_TOKEN="${MC_MACHINE_TOKEN:-}"
|
|
|
|
# Error handler
|
|
_error_exit() {
|
|
echo "❌ $1" >&2
|
|
return 1
|
|
}
|
|
|
|
# Make API call with machine token
|
|
_api_call() {
|
|
local method="$1"
|
|
local endpoint="$2"
|
|
local data="${3:-}"
|
|
|
|
if [[ -z "$MC_MACHINE_TOKEN" ]]; then
|
|
_error_exit "MC_MACHINE_TOKEN not set"
|
|
return 1
|
|
fi
|
|
|
|
local url="${MC_API_URL}${endpoint}"
|
|
local curl_opts=(
|
|
-s
|
|
-H "Content-Type: application/json"
|
|
-H "Authorization: Bearer ${MC_MACHINE_TOKEN}"
|
|
)
|
|
|
|
if [[ -n "$data" ]]; then
|
|
curl_opts+=(-d "$data")
|
|
fi
|
|
|
|
curl "${curl_opts[@]}" -X "$method" "$url" 2>/dev/null
|
|
}
|
|
|
|
# Detect folder based on content keywords (auto-categorization)
|
|
_detect_folder() {
|
|
local CONTENT="$1"
|
|
local FOLDER="Research/"
|
|
|
|
local LOWER_CONTENT=$(echo "$CONTENT" | tr '[:upper:]' '[:lower:]')
|
|
|
|
if [[ "$LOWER_CONTENT" =~ (ai|agent|llm|claude|openclaw|machine learning|gpt|model) ]]; then
|
|
FOLDER="Research/AI & Agents/"
|
|
elif [[ "$LOWER_CONTENT" =~ (swift|ios|xcode|apple|app store|uikit|swiftui) ]]; then
|
|
FOLDER="Research/iOS Development/"
|
|
elif [[ "$LOWER_CONTENT" =~ (saas|business|startup|marketing|revenue|growth|sales) ]]; then
|
|
FOLDER="Research/Business & Marketing/"
|
|
elif [[ "$LOWER_CONTENT" =~ (tool|library|api|package|sdk|framework|npm|pip) ]]; then
|
|
FOLDER="Research/Tools & Tech/"
|
|
elif [[ "$LOWER_CONTENT" =~ (tutorial|guide|how.to|walkthrough|step.by.step) ]]; then
|
|
FOLDER="Research/Tutorials/"
|
|
fi
|
|
|
|
echo "$FOLDER"
|
|
}
|
|
|
|
# Detect tags based on content keywords
|
|
_detect_tags() {
|
|
local CONTENT="$1"
|
|
local TAGS=("saved" "article")
|
|
|
|
local LOWER_CONTENT=$(echo "$CONTENT" | tr '[:upper:]' '[:lower:]')
|
|
|
|
[[ "$LOWER_CONTENT" =~ (ai|agent|automation|llm|model) ]] && TAGS+=("ai" "agents")
|
|
[[ "$LOWER_CONTENT" =~ openclaw ]] && TAGS+=("openclaw")
|
|
[[ "$LOWER_CONTENT" =~ (swift|ios) ]] && TAGS+=("ios" "swift")
|
|
[[ "$LOWER_CONTENT" =~ (business|saas) ]] && TAGS+=("business")
|
|
[[ "$LOWER_CONTENT" =~ tutorial ]] && TAGS+=("tutorial")
|
|
[[ "$LOWER_CONTENT" =~ (reference|documentation|docs) ]] && TAGS+=("reference")
|
|
|
|
printf '%s\n' "${TAGS[@]}" | jq -R . | jq -s 'unique'
|
|
}
|
|
|
|
# Create document
|
|
mc_doc_create() {
|
|
local TITLE=""
|
|
local CONTENT=""
|
|
local FOLDER=""
|
|
local TAGS=""
|
|
local TYPE="markdown"
|
|
local DESCRIPTION=""
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--title) TITLE="$2"; shift 2 ;;
|
|
--content) CONTENT="$2"; shift 2 ;;
|
|
--folder) FOLDER="$2"; shift 2 ;;
|
|
--tags) TAGS="$2"; shift 2 ;;
|
|
--type) TYPE="$2"; shift 2 ;;
|
|
--description) DESCRIPTION="$2"; shift 2 ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
# Validate
|
|
[[ -z "$TITLE" ]] && _error_exit "--title is required" && return 1
|
|
[[ -z "$CONTENT" ]] && _error_exit "--content is required" && return 1
|
|
|
|
# Clean content - remove control characters that break JSON
|
|
local CLEAN_CONTENT=$(echo "$CONTENT" | tr -d '\000-\031' | sed 's/\\/\\\\/g')
|
|
|
|
# Auto-detect folder/tags if not provided
|
|
[[ -z "$FOLDER" ]] && FOLDER=$(_detect_folder "$CONTENT")
|
|
[[ -z "$TAGS" ]] && TAGS=$(_detect_tags "$CONTENT")
|
|
[[ -z "$DESCRIPTION" ]] && DESCRIPTION="Created: $(date +%Y-%m-%d)"
|
|
|
|
# Build JSON payload
|
|
local TAGS_JSON
|
|
if [[ -n "$TAGS" ]]; then
|
|
TAGS_JSON="$TAGS"
|
|
else
|
|
TAGS_JSON=$(_detect_tags "$CONTENT")
|
|
fi
|
|
|
|
local PAYLOAD=$(jq -n \
|
|
--arg title "$TITLE" \
|
|
--arg content "$CLEAN_CONTENT" \
|
|
--arg type "$TYPE" \
|
|
--arg folder "$FOLDER" \
|
|
--argjson tags "$TAGS_JSON" \
|
|
--arg description "$DESCRIPTION" \
|
|
'{
|
|
title: $title,
|
|
content: $content,
|
|
type: $type,
|
|
folder: $folder,
|
|
tags: $tags,
|
|
description: $description
|
|
}')
|
|
|
|
# Create via API
|
|
local RESPONSE=$(_api_call "POST" "/documents" "$PAYLOAD")
|
|
|
|
local DOC_ID=$(echo "$RESPONSE" | jq -r '.id // .document?.id // empty')
|
|
|
|
if [[ -n "$DOC_ID" && "$DOC_ID" != "null" ]]; then
|
|
echo "$DOC_ID"
|
|
return 0
|
|
fi
|
|
|
|
_error_exit "Failed to create document: $(echo "$RESPONSE" | jq -r '.error // "Unknown error"')"
|
|
return 1
|
|
}
|
|
|
|
# Get document by ID
|
|
mc_doc_get() {
|
|
local DOC_ID="$1"
|
|
|
|
local RESPONSE=$(_api_call "GET" "/documents?id=$DOC_ID")
|
|
echo "$RESPONSE" | jq --arg id "$DOC_ID" '.documents | map(select(.id == $id)) | .[0] // empty'
|
|
}
|
|
|
|
# List documents with filters
|
|
mc_doc_list() {
|
|
local FOLDER=""
|
|
local TAG=""
|
|
local SEARCH=""
|
|
local LIMIT=50
|
|
local JSON_OUTPUT=false
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--folder) FOLDER="$2"; shift 2 ;;
|
|
--tags) TAG="$2"; shift 2 ;;
|
|
--search) SEARCH="$2"; shift 2 ;;
|
|
--limit) LIMIT="$2"; shift 2 ;;
|
|
--json) JSON_OUTPUT=true; shift ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
# Get documents via API
|
|
local RESPONSE=$(_api_call "GET" "/documents")
|
|
local DOCUMENTS=$(echo "$RESPONSE" | jq '.documents // []')
|
|
|
|
# Apply filters in jq
|
|
if [[ -n "$FOLDER" ]]; then
|
|
DOCUMENTS=$(echo "$DOCUMENTS" | jq --arg f "$FOLDER" 'map(select(.folder == $f))')
|
|
fi
|
|
|
|
if [[ -n "$TAG" ]]; then
|
|
DOCUMENTS=$(echo "$DOCUMENTS" | jq --arg t "$TAG" 'map(select(.tags | index($t)))')
|
|
fi
|
|
|
|
if [[ -n "$SEARCH" ]]; then
|
|
DOCUMENTS=$(echo "$DOCUMENTS" | jq --arg s "$SEARCH" 'map(select(.title | contains($s) or .content | contains($s)))')
|
|
fi
|
|
|
|
# Apply limit
|
|
DOCUMENTS=$(echo "$DOCUMENTS" | jq --argjson limit "$LIMIT" '.[:$limit]')
|
|
|
|
if [[ "$JSON_OUTPUT" == true ]]; then
|
|
echo "{\"documents\": $DOCUMENTS}"
|
|
else
|
|
echo "$DOCUMENTS" | jq -r '.[] | "\(.folder) | \(.title) | \(.id)"' 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
# Update document
|
|
mc_doc_update() {
|
|
local DOC_ID="$1"
|
|
shift
|
|
|
|
# First get the current document
|
|
local CURRENT_DOC=$(mc_doc_get "$DOC_ID")
|
|
[[ -z "$CURRENT_DOC" ]] && _error_exit "Document not found: $DOC_ID" && return 1
|
|
|
|
# Build updates
|
|
local UPDATES="{}"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--title) UPDATES=$(echo "$UPDATES" | jq --arg v "$2" '. + {title: $v}'); shift 2 ;;
|
|
--content) UPDATES=$(echo "$UPDATES" | jq --arg v "$2" '. + {content: $v}'); shift 2 ;;
|
|
--folder) UPDATES=$(echo "$UPDATES" | jq --arg v "$2" '. + {folder: $v}'); shift 2 ;;
|
|
--tags) UPDATES=$(echo "$UPDATES" | jq --argjson v "$2" '. + {tags: $v}'); shift 2 ;;
|
|
--description) UPDATES=$(echo "$UPDATES" | jq --arg v "$2" '. + {description: $v}'); shift 2 ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
# Merge current document with updates
|
|
local MERGED_DOC=$(echo "$CURRENT_DOC" | jq --argjson updates "$UPDATES" '. + $updates')
|
|
|
|
# Send update via API
|
|
local PAYLOAD=$(jq -n --arg id "$DOC_ID" --argjson doc "$MERGED_DOC" '{id: $id, document: $doc}')
|
|
local RESPONSE=$(_api_call "PATCH" "/documents" "$PAYLOAD")
|
|
|
|
if echo "$RESPONSE" | jq -e '.error' >/dev/null 2>&1; then
|
|
_error_exit "Update failed: $(echo "$RESPONSE" | jq -r '.error')"
|
|
return 1
|
|
fi
|
|
|
|
echo "✅ Document updated"
|
|
}
|
|
|
|
# Delete document
|
|
mc_doc_delete() {
|
|
local DOC_ID="$1"
|
|
|
|
_api_call "DELETE" "/documents" "{\"id\": \"$DOC_ID\"}" >/dev/null
|
|
|
|
echo "✅ Document deleted"
|
|
}
|
|
|
|
# Search documents
|
|
mc_doc_search() {
|
|
local QUERY="$1"
|
|
local LIMIT="${2:-20}"
|
|
|
|
# Get documents and filter client-side
|
|
local RESPONSE=$(_api_call "GET" "/documents")
|
|
|
|
echo "$RESPONSE" | jq --arg q "$QUERY" --argjson limit "$LIMIT" '
|
|
.documents
|
|
| map(select(.title | test($q; "i") or .content | test($q; "i")))
|
|
| .[:$limit]
|
|
'
|
|
}
|
|
|
|
# List folders
|
|
mc_doc_folder_list() {
|
|
# Get all documents and extract unique folders
|
|
local RESPONSE=$(_api_call "GET" "/documents")
|
|
|
|
echo "$RESPONSE" | jq -r '.documents[].folder' | sort -u
|
|
}
|
|
|
|
# Export functions
|
|
export -f _detect_folder
|
|
export -f _detect_tags
|
|
export -f _api_call
|
|
export -f mc_doc_create
|
|
export -f mc_doc_get
|
|
export -f mc_doc_list
|
|
export -f mc_doc_update
|
|
export -f mc_doc_delete
|
|
export -f mc_doc_search
|
|
export -f mc_doc_folder_list
|