2026-10-05 15:50:24 +00:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# NetVM native interactive muse CLI wrapper
|
|
|
|
|
# Enforces account/node selection, verifies/refreshes authentication cookies,
|
|
|
|
|
# and executes commands in the dedicated network namespace.
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
SCRIPT_SRC="${BASH_SOURCE[0]}"
|
|
|
|
|
while [ -h "$SCRIPT_SRC" ]; do
|
|
|
|
|
DIR="$(cd -P "$(dirname "$SCRIPT_SRC")" && pwd)"
|
|
|
|
|
SCRIPT_SRC="$(readlink "$SCRIPT_SRC")"
|
|
|
|
|
[[ $SCRIPT_SRC != /* ]] && SCRIPT_SRC="$DIR/$SCRIPT_SRC"
|
|
|
|
|
done
|
|
|
|
|
NETVM_BIN="$(cd -P "$(dirname "$SCRIPT_SRC")" && pwd)"
|
|
|
|
|
VALID_ACCOUNTS=("muse" "pip" "646" "opm" "def" "dev")
|
|
|
|
|
|
|
|
|
|
show_usage() {
|
2026-10-05 16:02:46 +00:00
|
|
|
echo "Usage: muse <account> <command> [arguments...]"
|
|
|
|
|
echo " muse -a <account> <command> [arguments...]"
|
2026-10-05 15:50:24 +00:00
|
|
|
echo ""
|
|
|
|
|
echo "Available accounts:"
|
|
|
|
|
for acct in "${VALID_ACCOUNTS[@]}"; do
|
|
|
|
|
echo " • $acct"
|
|
|
|
|
done
|
|
|
|
|
echo ""
|
|
|
|
|
echo "Common commands:"
|
2026-10-05 15:58:33 +00:00
|
|
|
echo " chat [--thread <id>] Launch interactive conversational shell / REPL"
|
2026-10-05 15:50:24 +00:00
|
|
|
echo " status Check agent status, sessions, and unread"
|
|
|
|
|
echo " threads List active threads and sidechats"
|
|
|
|
|
echo " history --thread <id> View message history"
|
|
|
|
|
echo " send --thread <id> msg Send message to an agent"
|
|
|
|
|
echo " unread View unread counts"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "Authentication & Cookie Management:"
|
|
|
|
|
echo " Cookies are isolated per-node in ~/.config/muse-cli/<account>/"
|
|
|
|
|
echo " If expired, the CLI attempts automated CDP cookie extraction."
|
|
|
|
|
echo ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ACCOUNT=""
|
|
|
|
|
POSITIONAL=()
|
|
|
|
|
|
2026-10-05 16:05:50 +00:00
|
|
|
# If first argument does not start with '-', treat it as the account candidate
|
|
|
|
|
if [[ $# -gt 0 && "$1" != -* ]]; then
|
|
|
|
|
ACCOUNT="$1"
|
|
|
|
|
shift
|
2026-10-05 16:02:46 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-10-05 15:50:24 +00:00
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
-a|--account)
|
|
|
|
|
if [[ -z "${2:-}" ]]; then
|
|
|
|
|
echo "Error: --account requires an argument." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
ACCOUNT="$2"
|
|
|
|
|
shift 2
|
|
|
|
|
;;
|
|
|
|
|
--account=*)
|
|
|
|
|
ACCOUNT="${1#*=}"
|
|
|
|
|
shift
|
|
|
|
|
;;
|
|
|
|
|
-h|--help)
|
|
|
|
|
if [[ -z "$ACCOUNT" && ${#POSITIONAL[@]} -eq 0 ]]; then
|
|
|
|
|
show_usage
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
POSITIONAL+=("$1")
|
|
|
|
|
shift
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
POSITIONAL+=("$1")
|
|
|
|
|
shift
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [[ -z "$ACCOUNT" ]]; then
|
2026-10-05 16:02:46 +00:00
|
|
|
echo "Error: No account specified. Specify account as first argument or with -a/--account." >&2
|
2026-10-05 15:50:24 +00:00
|
|
|
echo ""
|
|
|
|
|
show_usage
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
VALID=0
|
|
|
|
|
for acct in "${VALID_ACCOUNTS[@]}"; do
|
|
|
|
|
if [[ "$ACCOUNT" == "$acct" ]]; then
|
|
|
|
|
VALID=1
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [[ $VALID -eq 0 ]]; then
|
|
|
|
|
echo "Error: Invalid account '$ACCOUNT'." >&2
|
|
|
|
|
echo ""
|
|
|
|
|
show_usage
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ ${#POSITIONAL[@]} -eq 0 ]]; then
|
|
|
|
|
POSITIONAL=("status")
|
|
|
|
|
fi
|
|
|
|
|
|
2026-10-05 15:58:33 +00:00
|
|
|
# If subcommand is 'chat', launch interactive chat REPL
|
|
|
|
|
if [[ "${POSITIONAL[0]}" == "chat" ]]; then
|
|
|
|
|
shift_args=("${POSITIONAL[@]:1}")
|
|
|
|
|
exec python3 "$NETVM_BIN/muse-chat-repl.py" "$ACCOUNT" "${shift_args[@]}"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-10-05 15:50:24 +00:00
|
|
|
# Execute command; if auth fails and auto-cdp fails, offer interactive OTP sign-in if running interactively
|
|
|
|
|
set +e
|
|
|
|
|
"$NETVM_BIN/muse-cli-node" "$ACCOUNT" "${POSITIONAL[@]}"
|
|
|
|
|
RC=$?
|
|
|
|
|
set -e
|
|
|
|
|
|
|
|
|
|
if [[ $RC -ne 0 && -t 0 ]]; then
|
|
|
|
|
# Check if failure is authentication related
|
|
|
|
|
echo ""
|
|
|
|
|
echo "Notice: muse-cli command failed with exit code $RC."
|
|
|
|
|
echo "Would you like to initiate an interactive login for account '$ACCOUNT'? [y/N] "
|
|
|
|
|
read -r response
|
|
|
|
|
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
|
|
|
|
|
# Determine email for account if available
|
|
|
|
|
EMAIL=$(grep -E "^\|[[:space:]]*$ACCOUNT[[:space:]]*\|" "$NETVM_BIN/../ACCOUNTS.md" | awk -F '|' '{print $7}' | tr -d ' ' || true)
|
|
|
|
|
if [[ -z "$EMAIL" || "$EMAIL" == "-" ]]; then
|
|
|
|
|
echo -n "Enter email for account '$ACCOUNT': "
|
|
|
|
|
read -r EMAIL
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "Starting sign-in for $EMAIL..."
|
|
|
|
|
set +e
|
|
|
|
|
"$NETVM_BIN/muse-signin.py" --email "$EMAIL"
|
|
|
|
|
SIGNIN_RC=$?
|
|
|
|
|
set -e
|
|
|
|
|
|
|
|
|
|
if [[ $SIGNIN_RC -eq 2 ]]; then
|
|
|
|
|
echo -n "Enter the OTP code received: "
|
|
|
|
|
read -r OTP_CODE
|
|
|
|
|
"$NETVM_BIN/muse-signin.py" --email "$EMAIL" --otp "$OTP_CODE"
|
|
|
|
|
echo "Extracting cookies for $ACCOUNT..."
|
|
|
|
|
"$NETVM_BIN/refresh-node-cookies.py" "$ACCOUNT"
|
|
|
|
|
echo "Retrying command..."
|
|
|
|
|
exec "$NETVM_BIN/muse-cli-node" "$ACCOUNT" "${POSITIONAL[@]}"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
exit $RC
|