Implement S7 operator connect helpers for SaaS and channels.

Add dry-run-default connect dispatcher and per-integration scripts with gitignored local capability state, docs, and unit tests. Mutations require --apply and use nemohermes/openshell only.
This commit is contained in:
Ty
2026-07-27 13:32:42 -07:00
parent 76df8b7ab6
commit d6b74c42f6
17 changed files with 2229 additions and 35 deletions
+284
View File
@@ -0,0 +1,284 @@
#!/usr/bin/env bash
# scripts/connect/connect-channels.sh — S7: Channels connect helper
#
# Connects messaging channels (WhatsApp, Email, Telegram) via nemohermes.
#
# Platform-first: all mutations via nemohermes channels commands.
#
# Usage:
# ./scripts/connect/connect-channels.sh [--dry-run|--apply] [--target whatsapp|email|telegram]
# ./scripts/connect/connect-channels.sh --help
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source shared helpers
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# shellcheck source=../lib/env.sh
source "$SCRIPT_DIR/../lib/env.sh"
# shellcheck source=../lib/connect_state.sh
source "$SCRIPT_DIR/../lib/connect_state.sh"
# ── Defaults ───────────────────────────────────────────────────────────────
DRY_RUN=1
APPLY=0
TARGET=""
# All available channels
ALL_CHANNELS="whatsapp email telegram"
# ── Parse args ─────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--help|-h)
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Connect messaging channels via nemohermes.
Channels:
whatsapp WhatsApp Business API channel
email Email channel (thread-based interaction)
telegram Telegram bot channel
Options:
--dry-run Preview only (default)
--apply Execute mutations
--target <channel> Connect specific channel (default: all)
--help Show this help
Safety:
--dry-run is the default. Use --apply to perform mutations.
Channel changes may require a sandbox rebuild per the NemoClaw runtime matrix.
Examples:
$(basename "$0") --dry-run # preview all channels
$(basename "$0") --target whatsapp --apply # connect WhatsApp only
$(basename "$0") --apply # connect all channels
EOF
exit 0
;;
--dry-run)
DRY_RUN=1
APPLY=0
shift
;;
--apply)
DRY_RUN=0
APPLY=1
shift
;;
--target)
shift
TARGET="${1:-}"
if [[ -z "$TARGET" ]]; then
log_error "--target requires a value (whatsapp|email|telegram)"
exit 1
fi
# Validate target
valid=0
for ch in $ALL_CHANNELS; do
if [[ "$ch" == "$TARGET" ]]; then
valid=1
break
fi
done
if [[ $valid -eq 0 ]]; then
log_error "Invalid channel: $TARGET"
log_error "Valid channels: $ALL_CHANNELS"
exit 1
fi
shift
;;
*)
log_error "Unknown argument: $1"
exit 1
;;
esac
done
# ── Load .env ──────────────────────────────────────────────────────────────
load_env
SANDBOX_NAME="$(get_sandbox_name)"
log_section "Channels Connect"
# ── Check prerequisites ────────────────────────────────────────────────────
require_cmd nemohermes "Install nemohermes CLI (part of NemoClaw platform)"
if ! nemohermes "$SANDBOX_NAME" status &>/dev/null 2>&1; then
log_error "Sandbox '$SANDBOX_NAME' not found. Run S4 first."
exit 1
fi
# ── Determine which channels to process ────────────────────────────────────
if [[ -n "$TARGET" ]]; then
CHANNELS_TO_PROCESS="$TARGET"
else
CHANNELS_TO_PROCESS="$ALL_CHANNELS"
fi
# ── Channel connect functions ──────────────────────────────────────────────
connect_whatsapp() {
log_section "WhatsApp Channel"
local current_status
current_status="$(get_status "whatsapp")"
log_info "Current status: ${current_status:-not configured}"
if [[ $DRY_RUN -eq 1 ]]; then
log_info "[DRY-RUN] Would configure WhatsApp channel."
log_info "Steps:"
log_info " 1. Owner completes WhatsApp Business API setup in Meta developer console"
log_info " 2. Operator runs: nemohermes $SANDBOX_NAME channels add whatsapp --phone-number-id <ID> --verify-token <TOKEN>"
log_info " 3. Sandbox rebuild may be required per runtime matrix"
log_info ""
log_info "To execute: $(basename "$0") --target whatsapp --apply"
return 0
fi
# Apply mode: prompt for credentials
log_info "WhatsApp channel configuration:"
log_info "The owner must first set up WhatsApp Business API in the Meta developer console."
log_info "You will need the Phone Number ID and a Verify Token."
log_info ""
local phone_number_id=""
local verify_token=""
read -rp "Phone Number ID: " phone_number_id
if [[ -z "$phone_number_id" ]]; then
log_warn "No Phone Number ID provided. Skipping WhatsApp."
set_status "whatsapp" "skipped" "Operator skipped — no Phone Number ID"
return 0
fi
# Read verify token silently
read -rsp "Verify Token: " verify_token
echo ""
if [[ -z "$verify_token" ]]; then
log_warn "No Verify Token provided. Skipping WhatsApp."
set_status "whatsapp" "skipped" "Operator skipped — no Verify Token"
return 0
fi
log_info "Adding WhatsApp channel…"
# NOTE: Verify token passed as CLI arg is briefly visible in /proc/*/cmdline.
# This is a platform limitation of nemohermes which does not yet support
# --from-stdin for channel credentials.
if nemohermes "$SANDBOX_NAME" channels add whatsapp \
--phone-number-id "$phone_number_id" \
--verify-token "$verify_token" 2>&1; then
log_info "WhatsApp channel added successfully."
set_status "whatsapp" "connected" "WhatsApp channel active"
else
log_warn "WhatsApp channel add failed (may need rebuild)."
set_status "whatsapp" "error" "WhatsApp channel add failed — check nemohermes output"
fi
}
connect_email() {
log_section "Email Channel"
local current_status
current_status="$(get_status "email")"
log_info "Current status: ${current_status:-not configured}"
if [[ $DRY_RUN -eq 1 ]]; then
log_info "[DRY-RUN] Would configure Email channel."
log_info "Steps:"
log_info " 1. Owner provides email address for the assistant"
log_info " 2. Operator runs: nemohermes $SANDBOX_NAME channels add email --address <EMAIL>"
log_info " 3. Sandbox rebuild may be required per runtime matrix"
log_info ""
log_info "To execute: $(basename "$0") --target email --apply"
return 0
fi
log_info "Email channel configuration:"
log_info "The owner provides the email address the assistant will respond to."
log_info ""
local email_address=""
read -rp "Email address for assistant: " email_address
if [[ -z "$email_address" ]]; then
log_warn "No email address provided. Skipping Email."
set_status "email" "skipped" "Operator skipped — no email address"
return 0
fi
log_info "Adding Email channel…"
if nemohermes "$SANDBOX_NAME" channels add email \
--address "$email_address" 2>&1; then
log_info "Email channel added successfully."
set_status "email" "connected" "Email channel active"
else
log_warn "Email channel add failed (may need rebuild)."
set_status "email" "error" "Email channel add failed — check nemohermes output"
fi
}
connect_telegram() {
log_section "Telegram Channel"
local current_status
current_status="$(get_status "telegram")"
log_info "Current status: ${current_status:-not configured}"
if [[ $DRY_RUN -eq 1 ]]; then
log_info "[DRY-RUN] Would configure Telegram channel."
log_info "Steps:"
log_info " 1. Owner creates a bot via @BotFather on Telegram"
log_info " 2. BotFather returns a Bot Token"
log_info " 3. Operator runs: nemohermes $SANDBOX_NAME channels add telegram --bot-token <TOKEN>"
log_info " 4. Sandbox rebuild may be required per runtime matrix"
log_info ""
log_info "To execute: $(basename "$0") --target telegram --apply"
return 0
fi
log_info "Telegram channel configuration:"
log_info "The owner must first create a bot via @BotFather on Telegram."
log_info "BotFather returns a Bot Token."
log_info ""
local bot_token=""
read -rsp "Telegram Bot Token: " bot_token
echo ""
if [[ -z "$bot_token" ]]; then
log_warn "No Bot Token provided. Skipping Telegram."
set_status "telegram" "skipped" "Operator skipped — no Bot Token"
return 0
fi
log_info "Adding Telegram channel…"
# NOTE: Bot token passed as CLI arg is briefly visible in /proc/*/cmdline.
# This is a platform limitation of nemohermes which does not yet support
# --from-stdin for channel credentials.
if nemohermes "$SANDBOX_NAME" channels add telegram \
--bot-token "$bot_token" 2>&1; then
log_info "Telegram channel added successfully."
set_status "telegram" "connected" "Telegram channel active"
else
log_warn "Telegram channel add failed (may need rebuild)."
set_status "telegram" "error" "Telegram channel add failed — check nemohermes output"
fi
}
# ── Execute ────────────────────────────────────────────────────────────────
for channel in $CHANNELS_TO_PROCESS; do
case "$channel" in
whatsapp) connect_whatsapp ;;
email) connect_email ;;
telegram) connect_telegram ;;
*)
log_error "Unknown channel: $channel"
exit 1
;;
esac
done
log_info "Channels connect complete."
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env bash
# scripts/connect/connect-name.sh — S7: Name / profile connect helper
#
# Guides the operator through naming the assistant and setting the profile.
# The assistant name becomes the NemoClaw sandbox display identity.
#
# Platform-first: uses nemohermes for sandbox operations.
#
# Usage:
# ./scripts/connect/connect-name.sh [--dry-run|--apply]
# ./scripts/connect/connect-name.sh --help
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source shared helpers
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# shellcheck source=../lib/env.sh
source "$SCRIPT_DIR/../lib/env.sh"
# shellcheck source=../lib/connect_state.sh
source "$SCRIPT_DIR/../lib/connect_state.sh"
# ── Defaults ───────────────────────────────────────────────────────────────
DRY_RUN=1
APPLY=0
# ── Parse args ─────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--help|-h)
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Name / profile connect helper.
Sets the assistant name (sandbox display identity) and verifies the profile
is configured. The assistant name is stored in LUMINA_SANDBOX in .env.
Options:
--dry-run Preview only (default)
--apply Execute mutations
--help Show this help
Safety:
--dry-run is the default. Use --apply to perform mutations.
Renaming an existing sandbox may require a rebuild.
EOF
exit 0
;;
--dry-run)
DRY_RUN=1
APPLY=0
shift
;;
--apply)
DRY_RUN=0
APPLY=1
shift
;;
*)
log_error "Unknown argument: $1"
exit 1
;;
esac
done
# ── Load .env ──────────────────────────────────────────────────────────────
load_env
SANDBOX_NAME="$(get_sandbox_name)"
log_section "Name / Profile Setup"
# ── Current state ──────────────────────────────────────────────────────────
current_status="$(get_status "name")"
if [[ -n "$current_status" ]]; then
log_info "Current name status: $current_status"
fi
log_info "Current sandbox name: $SANDBOX_NAME"
# ── Check sandbox exists ───────────────────────────────────────────────────
if nemohermes_available; then
if nemohermes "$SANDBOX_NAME" status &>/dev/null 2>&1; then
log_info "Sandbox '$SANDBOX_NAME' is reachable."
else
log_warn "Sandbox '$SANDBOX_NAME' not found. Run S4 (sandbox onboard) first."
if [[ $APPLY -eq 1 ]]; then
set_status "name" "error" "Sandbox not found — run S4 first"
fi
exit 1
fi
else
log_warn "nemohermes not available — cannot verify sandbox"
fi
# ── Name guidance ──────────────────────────────────────────────────────────
log_info ""
log_info "The assistant name is the display identity the owner sees."
log_info "It is stored as LUMINA_SANDBOX in .env and used by nemohermes."
log_info ""
log_info "Platform naming rules:"
log_info " - Lowercase letters, digits, hyphens, underscores"
log_info " - 1-63 characters"
log_info " - Must be unique within the NemoClaw registry"
log_info ""
# ── Rename (if --apply and operator wants to change) ───────────────────────
if [[ $APPLY -eq 1 ]]; then
log_info "Current name: $SANDBOX_NAME"
log_info ""
log_info "To rename the assistant:"
log_info " 1. Edit LUMINA_SANDBOX in .env"
log_info " 2. Run: nemohermes <new-name> onboard (creates new sandbox)"
log_info " 3. Re-run S5 (policy + skills) for the new sandbox"
log_info ""
log_warn "Renaming requires creating a new sandbox. The old sandbox is NOT"
log_warn "automatically deleted. Delete it manually if no longer needed."
log_info ""
# For now, we record the name as connected if sandbox exists
set_status "name" "connected" "Sandbox '$SANDBOX_NAME' verified"
log_info "Name status recorded as connected."
else
log_info "[DRY-RUN] Would verify sandbox name and record status."
log_info "Use --apply to record the name status."
fi
# ── Profile guidance ───────────────────────────────────────────────────────
log_info ""
log_info "Profile intake (business name, timezone, hours, priorities, hard rules)"
log_info "is handled through the setup-education skill in the assistant chat."
log_info "The operator does not need to configure this via scripts."
if [[ $APPLY -eq 1 ]]; then
# Check if profile status is already set
profile_status="$(get_status "profile")"
if [[ -z "$profile_status" ]]; then
# Default: profile is handled by owner in chat, not operator script
set_status "profile" "later" "Profile intake via setup-education skill in chat"
fi
fi
log_info "Name / profile connect complete."
+285
View File
@@ -0,0 +1,285 @@
#!/usr/bin/env bash
# scripts/connect/connect-quickbooks.sh — S7: QuickBooks Online connect helper
#
# Connects QuickBooks Online via local MCP (intuit/quickbooks-online-mcp-server).
# The MCP server runs as a container on the same Docker network as Hermes.
#
# Allows: reports, search/get invoices, bills, vendors, customers, company info.
# Denies: create_payment, bill_payment, money movement; write/update/delete off for MVP.
#
# Platform-first: uses nemohermes config set for MCP registration.
#
# Usage:
# ./scripts/connect/connect-quickbooks.sh [--dry-run|--apply]
# ./scripts/connect/connect-quickbooks.sh --help
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source shared helpers
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# shellcheck source=../lib/env.sh
source "$SCRIPT_DIR/../lib/env.sh"
# shellcheck source=../lib/connect_state.sh
source "$SCRIPT_DIR/../lib/connect_state.sh"
# ── Defaults ───────────────────────────────────────────────────────────────
DRY_RUN=1
APPLY=0
# QBO MCP configuration
QBO_MCP_IMAGE="ghcr.io/intuit/quickbooks-online-mcp-server:latest"
QBO_MCP_CONTAINER="lumina-qbo-mcp"
QBO_MCP_NETWORK="lumina-network"
# Allowed tools (read-only)
QBO_ALLOWED_TOOLS=(
"get_report"
"search_invoice"
"get_invoice"
"search_bill"
"get_bill"
"search_vendor"
"get_vendor"
"search_customer"
"get_customer"
"get_company_info"
)
# Denied tools
QBO_DENIED_TOOLS=(
"create_payment"
"bill_payment"
"create_invoice"
"update_invoice"
"delete_invoice"
"create_bill"
"update_bill"
"delete_bill"
)
# ── Parse args ─────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--help|-h)
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Connect QuickBooks Online via local MCP.
QBO MCP:
Image: $QBO_MCP_IMAGE
Container: $QBO_MCP_CONTAINER
Network: $QBO_MCP_NETWORK (same Docker network as Hermes)
Type: Local MCP (stdio or network-attached)
Allowed tools (read-only):
reports, search/get invoices, bills, vendors, customers, company info
Denied tools:
create_payment, bill_payment, write/update/delete operations
Options:
--dry-run Preview only (default)
--apply Execute mutations (prompts for QBO credentials)
--help Show this help
Safety:
--dry-run is the default. Use --apply to perform mutations.
QBO credentials are stored via OpenShell provider store — never in .env.
Payment tools are explicitly denied in the MCP tool filter.
Write/update/delete operations are disabled for MVP.
Examples:
$(basename "$0") --dry-run
$(basename "$0") --apply
EOF
exit 0
;;
--dry-run)
DRY_RUN=1
APPLY=0
shift
;;
--apply)
DRY_RUN=0
APPLY=1
shift
;;
*)
log_error "Unknown argument: $1"
exit 1
;;
esac
done
# ── Load .env ──────────────────────────────────────────────────────────────
load_env
SANDBOX_NAME="$(get_sandbox_name)"
log_section "QuickBooks Online Connect"
# ── Current state ──────────────────────────────────────────────────────────
current_status="$(get_status "quickbooks")"
if [[ -n "$current_status" ]]; then
log_info "Current status: $current_status"
fi
# ── Check prerequisites ────────────────────────────────────────────────────
require_cmd nemohermes "Install nemohermes CLI (part of NemoClaw platform)"
require_cmd docker "Docker required for local MCP container"
if ! nemohermes "$SANDBOX_NAME" status &>/dev/null 2>&1; then
log_error "Sandbox '$SANDBOX_NAME' not found. Run S4 first."
exit 1
fi
# ── Dry-run mode ───────────────────────────────────────────────────────────
if [[ $DRY_RUN -eq 1 ]]; then
log_info "[DRY-RUN] QuickBooks Online connect preview:"
log_info ""
log_info " MCP Image: $QBO_MCP_IMAGE"
log_info " Container: $QBO_MCP_CONTAINER"
log_info " Network: $QBO_MCP_NETWORK"
log_info " Sandbox: $SANDBOX_NAME"
log_info ""
log_info " Allowed tools (read-only):"
for tool in "${QBO_ALLOWED_TOOLS[@]}"; do
log_info " - $tool"
done
log_info ""
log_info " Denied tools:"
for tool in "${QBO_DENIED_TOOLS[@]}"; do
log_info " - $tool"
done
log_info ""
log_info " Steps to connect:"
log_info " 1. Owner creates QBO app at developer.intuit.com"
log_info " 2. Owner completes OAuth flow to get access token"
log_info " 3. Operator stores credentials: openshell provider set qbo-* <VALUES>"
log_info " 4. Operator starts MCP container on Docker network"
log_info " 5. Operator registers MCP: nemohermes $SANDBOX_NAME config set mcp_servers.quickbooks"
log_info " 6. Tool allowlist/denylist applied via nemohermes config"
log_info ""
log_info " To execute: $(basename "$0") --apply"
exit 0
fi
# ── Apply mode ─────────────────────────────────────────────────────────────
log_info "QuickBooks Online connect (apply mode):"
log_info ""
log_info "The owner must first:"
log_info " 1. Create a QBO application at developer.intuit.com"
log_info " 2. Complete OAuth flow to obtain Client ID, Client Secret, and Access Token"
log_info " 3. Provide credentials to the operator"
log_info ""
# Store credentials via OpenShell provider store
if openshell_available; then
log_info "Storing QBO credentials in OpenShell provider store…"
local_client_id=""
local_client_secret=""
local_access_token=""
local_realm_id=""
read -rp "QBO Client ID: " local_client_id
read -rsp "QBO Client Secret: " local_client_secret
echo ""
read -rsp "QBO Access Token: " local_access_token
echo ""
read -rp "QBO Realm ID (company ID): " local_realm_id
if [[ -z "$local_client_id" || -z "$local_client_secret" || -z "$local_access_token" ]]; then
log_warn "Incomplete QBO credentials. Skipping."
set_status "quickbooks" "skipped" "Operator skipped — incomplete credentials"
exit 0
fi
# Store each credential (never echo them). Track failures.
# NOTE: Credentials passed as CLI args to openshell are briefly visible in
# /proc/*/cmdline and ps output. This is a platform limitation of openshell
# which does not yet support --from-stdin for provider values.
store_fail=0
if openshell provider set qbo-client-id "$local_client_id" 2>&1; then
log_info " qbo-client-id stored."
else
log_warn " qbo-client-id store failed — check openshell provider store."
store_fail=1
fi
if openshell provider set qbo-client-secret "$local_client_secret" 2>&1; then
log_info " qbo-client-secret stored."
else
log_warn " qbo-client-secret store failed — check openshell provider store."
store_fail=1
fi
if openshell provider set qbo-access-token "$local_access_token" 2>&1; then
log_info " qbo-access-token stored."
else
log_warn " qbo-access-token store failed — check openshell provider store."
store_fail=1
fi
if [[ -n "$local_realm_id" ]]; then
if openshell provider set qbo-realm-id "$local_realm_id" 2>&1; then
log_info " qbo-realm-id stored."
else
log_warn " qbo-realm-id store failed — check openshell provider store."
store_fail=1
fi
fi
if [[ $store_fail -eq 1 ]]; then
log_warn "Some credentials failed to store. Verify with: openshell provider list"
else
log_info "QBO credentials stored in provider store."
fi
else
log_warn "openshell not available. Cannot store credentials in provider store."
log_warn "Store manually: openshell provider set qbo-* <VALUES>"
fi
# Check if MCP container already exists
if docker ps -a --format '{{.Names}}' | grep -q "^${QBO_MCP_CONTAINER}$"; then
log_info "QBO MCP container '$QBO_MCP_CONTAINER' already exists."
log_info "To recreate: docker rm -f $QBO_MCP_CONTAINER"
else
log_info "QBO MCP container not yet created."
log_info "When ready, start with:"
log_info " docker run -d --name $QBO_MCP_CONTAINER \\"
log_info " --network $QBO_MCP_NETWORK \\"
log_info " -e QB_CLIENT_ID=<CLIENT_ID> \\"
log_info " -e QB_CLIENT_SECRET=<CLIENT_SECRET> \\"
log_info " -e QB_ACCESS_TOKEN=<ACCESS_TOKEN> \\"
log_info " -e QB_REALM_ID=<REALM_ID> \\"
log_info " $QBO_MCP_IMAGE"
log_info ""
log_info "Or add to deploy/compose/docker-compose.yml for managed lifecycle."
fi
# Register MCP server in Hermes config
log_info "Registering QBO MCP server in Hermes config…"
if nemohermes "$SANDBOX_NAME" config set \
mcp_servers.quickbooks.type "local" 2>&1; then
log_info "QBO MCP type registered."
else
log_warn "MCP config registration may need manual setup."
log_warn "Manual: nemohermes $SANDBOX_NAME config set mcp_servers.quickbooks"
fi
# Apply tool allowlist
log_info "Applying QBO tool allowlist (read-only)…"
log_info " Allowed: ${QBO_ALLOWED_TOOLS[*]}"
log_info " Denied: ${QBO_DENIED_TOOLS[*]}"
# Note: The actual tool filtering is done via mcp_servers config with
# tools.include / tools.exclude. The exact nemohermes subcommand varies
# by platform version. This is deferred until the platform CLI supports
# per-server tool filtering in a stable form.
log_info "[DEFERRED] Tool filters will be applied via nemohermes config set"
log_info " when the platform CLI supports per-server tools.include/exclude."
set_status "quickbooks" "connected" "QBO local MCP registered (read-only tools)"
log_info "QuickBooks Online connect complete."
+216
View File
@@ -0,0 +1,216 @@
#!/usr/bin/env bash
# scripts/connect/connect-square.sh — S7: Square connect helper
#
# Connects Square via remote MCP (mcp.squareup.com).
# Allows: bookings, customers, catalog, inventory/location reads.
# Denies: payments, refunds, cards, checkout, payouts.
#
# Platform-first: uses nemohermes config set for MCP registration.
#
# Usage:
# ./scripts/connect/connect-square.sh [--dry-run|--apply]
# ./scripts/connect/connect-square.sh --help
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source shared helpers
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# shellcheck source=../lib/env.sh
source "$SCRIPT_DIR/../lib/env.sh"
# shellcheck source=../lib/connect_state.sh
source "$SCRIPT_DIR/../lib/connect_state.sh"
# ── Defaults ───────────────────────────────────────────────────────────────
DRY_RUN=1
APPLY=0
# Square MCP configuration
SQUARE_MCP_URL="https://mcp.squareup.com/v1"
# Allowed tools (read-only)
SQUARE_ALLOWED_TOOLS=(
"bookings/list_bookings"
"bookings/get_booking"
"customers/list_customers"
"customers/get_customer"
"catalog/list_catalog"
"catalog/search_catalog_objects"
"inventory/list_inventory"
"locations/list_locations"
"locations/get_location"
)
# Denied tools (payment-related)
SQUARE_DENIED_TOOLS=(
"payments/*"
"refunds/*"
"cards/*"
"checkout/*"
"payouts/*"
)
# ── Parse args ─────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--help|-h)
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Connect Square via remote MCP.
Square MCP:
URL: $SQUARE_MCP_URL
Type: Remote MCP (HTTP/SSE)
Allowed tools (read-only):
bookings, customers, catalog, inventory, locations
Denied tools:
payments, refunds, cards, checkout, payouts
Options:
--dry-run Preview only (default)
--apply Execute mutations (prompts for Square access token)
--help Show this help
Safety:
--dry-run is the default. Use --apply to perform mutations.
The Square access token is stored via OpenShell provider store — never in .env.
Payment tools are explicitly denied in the MCP tool filter.
Examples:
$(basename "$0") --dry-run
$(basename "$0") --apply
EOF
exit 0
;;
--dry-run)
DRY_RUN=1
APPLY=0
shift
;;
--apply)
DRY_RUN=0
APPLY=1
shift
;;
*)
log_error "Unknown argument: $1"
exit 1
;;
esac
done
# ── Load .env ──────────────────────────────────────────────────────────────
load_env
SANDBOX_NAME="$(get_sandbox_name)"
log_section "Square Connect"
# ── Current state ──────────────────────────────────────────────────────────
current_status="$(get_status "square")"
if [[ -n "$current_status" ]]; then
log_info "Current status: $current_status"
fi
# ── Check prerequisites ────────────────────────────────────────────────────
require_cmd nemohermes "Install nemohermes CLI (part of NemoClaw platform)"
if ! nemohermes "$SANDBOX_NAME" status &>/dev/null 2>&1; then
log_error "Sandbox '$SANDBOX_NAME' not found. Run S4 first."
exit 1
fi
# ── Dry-run mode ───────────────────────────────────────────────────────────
if [[ $DRY_RUN -eq 1 ]]; then
log_info "[DRY-RUN] Square connect preview:"
log_info ""
log_info " MCP URL: $SQUARE_MCP_URL"
log_info " Type: Remote MCP"
log_info " Sandbox: $SANDBOX_NAME"
log_info ""
log_info " Allowed tools (read-only):"
for tool in "${SQUARE_ALLOWED_TOOLS[@]}"; do
log_info " - $tool"
done
log_info ""
log_info " Denied tools:"
for tool in "${SQUARE_DENIED_TOOLS[@]}"; do
log_info " - $tool"
done
log_info ""
log_info " Steps to connect:"
log_info " 1. Owner creates Square application at developer.squareup.com"
log_info " 2. Owner generates an OAuth access token (read scope)"
log_info " 3. Operator stores token: openshell provider set square-access-token <TOKEN>"
log_info " 4. Operator registers MCP: nemohermes $SANDBOX_NAME config set mcp_servers.square.url $SQUARE_MCP_URL"
log_info " 5. Tool allowlist/denylist applied via nemohermes config"
log_info ""
log_info " To execute: $(basename "$0") --apply"
exit 0
fi
# ── Apply mode ─────────────────────────────────────────────────────────────
log_info "Square connect (apply mode):"
log_info ""
log_info "The owner must first:"
log_info " 1. Create a Square application at developer.squareup.com"
log_info " 2. Generate an OAuth access token with read-only scope"
log_info " 3. Provide the token to the operator"
log_info ""
# Store token via OpenShell provider store
if openshell_available; then
log_info "Storing Square access token in OpenShell provider store…"
local_token=""
read -rsp "Square Access Token: " local_token
echo ""
if [[ -z "$local_token" ]]; then
log_warn "No access token provided. Skipping Square."
set_status "square" "skipped" "Operator skipped — no access token"
exit 0
fi
# Store via openshell (never echo the token).
# NOTE: Credentials passed as CLI args to openshell are briefly visible in
# /proc/*/cmdline and ps output. This is a platform limitation of openshell
# which does not yet support --from-stdin for provider values.
if openshell provider set square-access-token "$local_token" 2>&1; then
log_info "Square access token stored in provider store."
else
log_warn "Could not store token via openshell. Token may need manual setup."
log_warn "Run: openshell provider set square-access-token <TOKEN>"
fi
else
log_warn "openshell not available. Cannot store token in provider store."
log_warn "Store manually: openshell provider set square-access-token <TOKEN>"
fi
# Register MCP server
log_info "Registering Square MCP server…"
if nemohermes "$SANDBOX_NAME" config set \
mcp_servers.square.url "$SQUARE_MCP_URL" 2>&1; then
log_info "Square MCP URL registered."
else
log_warn "MCP URL registration failed (may need different config path)."
log_warn "Manual: nemohermes $SANDBOX_NAME config set mcp_servers.square.url $SQUARE_MCP_URL"
fi
# Apply tool allowlist
log_info "Applying Square tool allowlist (read-only)…"
log_info " Allowed: ${SQUARE_ALLOWED_TOOLS[*]}"
log_info " Denied: ${SQUARE_DENIED_TOOLS[*]}"
# Note: The actual tool filtering is done via mcp_servers config with
# tools.include / tools.exclude. The exact nemohermes subcommand varies
# by platform version. This is deferred until the platform CLI supports
# per-server tool filtering in a stable form.
log_info "[DEFERRED] Tool filters will be applied via nemohermes config set"
log_info " when the platform CLI supports per-server tools.include/exclude."
set_status "square" "connected" "Square remote MCP registered (read-only tools)"
log_info "Square connect complete."
+219
View File
@@ -0,0 +1,219 @@
#!/usr/bin/env bash
# scripts/connect/connect-vagaro.sh — S7: Vagaro connect helper
#
# Connects Vagaro via REST API + webhooks.
# No public MCP exists for Vagaro — our services handle the integration.
#
# Allows: appointments, clients, services, staff.
# Denies: no scrape, no unofficial access.
#
# Platform-first: credentials via OpenShell provider store.
#
# Usage:
# ./scripts/connect/connect-vagaro.sh [--dry-run|--apply]
# ./scripts/connect/connect-vagaro.sh --help
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source shared helpers
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# shellcheck source=../lib/env.sh
source "$SCRIPT_DIR/../lib/env.sh"
# shellcheck source=../lib/connect_state.sh
source "$SCRIPT_DIR/../lib/connect_state.sh"
# ── Defaults ───────────────────────────────────────────────────────────────
DRY_RUN=1
APPLY=0
# Vagaro API configuration
VAGARO_API_BASE="https://api.vagaro.com"
VAGARO_WEBHOOK_PORT="${LUMINA_VAGARO_WEBHOOK_PORT:-9876}"
# ── Parse args ─────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--help|-h)
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Connect Vagaro via REST API + webhooks.
Vagaro integration:
API Base: $VAGARO_API_BASE
Type: REST + webhooks (no public MCP)
Webhook port: $VAGARO_WEBHOOK_PORT
Allowed operations:
appointments, clients, services, staff
Denied:
No scrape, no unofficial access
Options:
--dry-run Preview only (default)
--apply Execute mutations (prompts for Vagaro credentials)
--help Show this help
Safety:
--dry-run is the default. Use --apply to perform mutations.
Vagaro credentials are stored via OpenShell provider store — never in .env.
No unofficial scraping — only documented REST API.
Examples:
$(basename "$0") --dry-run
$(basename "$0") --apply
EOF
exit 0
;;
--dry-run)
DRY_RUN=1
APPLY=0
shift
;;
--apply)
DRY_RUN=0
APPLY=1
shift
;;
*)
log_error "Unknown argument: $1"
exit 1
;;
esac
done
# ── Load .env ──────────────────────────────────────────────────────────────
load_env
SANDBOX_NAME="$(get_sandbox_name)"
log_section "Vagaro Connect"
# ── Current state ──────────────────────────────────────────────────────────
current_status="$(get_status "vagaro")"
if [[ -n "$current_status" ]]; then
log_info "Current status: $current_status"
fi
# ── Check prerequisites ────────────────────────────────────────────────────
require_cmd nemohermes "Install nemohermes CLI (part of NemoClaw platform)"
if ! nemohermes "$SANDBOX_NAME" status &>/dev/null 2>&1; then
log_error "Sandbox '$SANDBOX_NAME' not found. Run S4 first."
exit 1
fi
# ── Dry-run mode ───────────────────────────────────────────────────────────
if [[ $DRY_RUN -eq 1 ]]; then
log_info "[DRY-RUN] Vagaro connect preview:"
log_info ""
log_info " API Base: $VAGARO_API_BASE"
log_info " Webhook: port $VAGARO_WEBHOOK_PORT"
log_info " Sandbox: $SANDBOX_NAME"
log_info ""
log_info " Steps to connect:"
log_info " 1. Owner creates Vagaro developer account at developer.vagaro.com"
log_info " 2. Owner registers an application to get API credentials"
log_info " 3. Owner configures webhook URL in Vagaro dashboard"
log_info " 4. Operator stores credentials: openshell provider set vagaro-* <VALUES>"
log_info " 5. Operator verifies API connectivity"
log_info " 6. Webhook service started (compose or container)"
log_info ""
log_info " To execute: $(basename "$0") --apply"
exit 0
fi
# ── Apply mode ─────────────────────────────────────────────────────────────
log_info "Vagaro connect (apply mode):"
log_info ""
log_info "The owner must first:"
log_info " 1. Create a Vagaro developer account at developer.vagaro.com"
log_info " 2. Register an application to obtain API credentials"
log_info " 3. Configure webhook URL in the Vagaro dashboard"
log_info " 4. Provide credentials to the operator"
log_info ""
# Store credentials via OpenShell provider store
if ! openshell_available; then
log_error "openshell not available. Cannot store credentials in provider store."
log_error "Install openshell CLI, then re-run with --apply."
set_status "vagaro" "error" "openshell not available — cannot store credentials"
exit 1
fi
log_info "Storing Vagaro credentials in OpenShell provider store…"
local_api_key=""
local_api_secret=""
local_webhook_secret=""
# SECURITY: API key silenced to prevent shoulder-surfing and terminal-log leakage.
# NOTE: Credentials passed as CLI args to openshell are briefly visible in
# /proc/*/cmdline and ps output. This is a platform limitation of openshell
# which does not yet support --from-stdin for provider values.
read -rsp "Vagaro API Key: " local_api_key
echo ""
read -rsp "Vagaro API Secret: " local_api_secret
echo ""
read -rsp "Vagaro Webhook Secret (optional): " local_webhook_secret
echo ""
if [[ -z "$local_api_key" || -z "$local_api_secret" ]]; then
log_warn "Incomplete Vagaro credentials. Skipping."
set_status "vagaro" "skipped" "Operator skipped — incomplete credentials"
exit 0
fi
# Store credentials (never echo them). Track failures.
store_fail=0
if openshell provider set vagaro-api-key "$local_api_key" 2>&1; then
log_info " vagaro-api-key stored."
else
log_warn " vagaro-api-key store failed — check openshell provider store."
store_fail=1
fi
if openshell provider set vagaro-api-secret "$local_api_secret" 2>&1; then
log_info " vagaro-api-secret stored."
else
log_warn " vagaro-api-secret store failed — check openshell provider store."
store_fail=1
fi
if [[ -n "$local_webhook_secret" ]]; then
if openshell provider set vagaro-webhook-secret "$local_webhook_secret" 2>&1; then
log_info " vagaro-webhook-secret stored."
else
log_warn " vagaro-webhook-secret store failed — check openshell provider store."
store_fail=1
fi
fi
if [[ $store_fail -eq 1 ]]; then
log_warn "Some credentials failed to store. Verify with: openshell provider list"
fi
# Verify API connectivity (best-effort)
log_info "Verifying Vagaro API connectivity…"
if curl -sf --max-time 10 \
-H "X-Vagaro-API-Key: $local_api_key" \
"${VAGARO_API_BASE}/v1/me" &>/dev/null; then
log_info "Vagaro API connectivity verified."
set_status "vagaro" "connected" "Vagaro REST API connected"
else
log_warn "Vagaro API verification failed (credentials may be incorrect or API unavailable)."
log_warn "This may be non-fatal if the API requires specific scopes."
set_status "vagaro" "error" "Vagaro API verification failed — check credentials"
fi
# Webhook guidance
log_info ""
log_info "Webhook service:"
log_info " The Vagaro webhook receiver runs on port $VAGARO_WEBHOOK_PORT."
log_info " Configure the webhook URL in the Vagaro dashboard:"
log_info " https://<your-host>:$VAGARO_WEBHOOK_PORT/vagaro/webhook"
log_info " Or add to deploy/compose/docker-compose.yml for managed lifecycle."
log_info "Vagaro connect complete."