diff --git a/README.md b/README.md index 8b80525..dc3ab0c 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,22 @@ # menlo -A CLI tool for Menlo research and development. +A CLI tool for accessing Menlo Robot. -## Installation +## Quick Install ```bash -go install github.com/menloresearch/cli@latest +# Install +sh -c "$(curl -fsSL https://raw.githubusercontent.com/menloresearch/cli/release/install.sh)" + +# Uninstall +sh -c "$(curl -fsSL https://raw.githubusercontent.com/menloresearch/cli/release/uninstall.sh)" ``` Or build from source: ```bash git clone https://github.com/menloresearch/cli -cd menlo +cd cli go build -o menlo ./cmd/menlo ``` @@ -69,6 +73,15 @@ Available actions: - `turn-left` - Turn the robot left - `turn-right` - Turn the robot right +#### Create WebRTC session + +```bash +menlo robot session # Use default robot +menlo robot session --robot-id # Use specific robot +``` + +This opens a LiveKit meet session with the robot, returning an SFU endpoint, WebRTC token, and a join URL. + ### menlo config Manage configuration. @@ -104,15 +117,3 @@ Configuration is stored in: - Linux: `~/.config/menlo/config.yaml` - Windows: `%APPDATA%\menlo\config.yaml` -## Shell Completion - -```bash -# Bash -menlo completion bash > /etc/bash_completion.d/menlo - -# Zsh -menlo completion zsh > "${fpath[1]}/_menlo" - -# Fish -menlo completion fish > ~/.config/fish/completions/menlo.fish -``` \ No newline at end of file diff --git a/install.sh b/install.sh index 8dc09e2..38603f6 100755 --- a/install.sh +++ b/install.sh @@ -141,10 +141,13 @@ install() { fi log_info "Version $VERSION written to config" - # Install shell completions to config directory (not modifying user shell files) + # Install shell completions and add to shell rc file COMPLETION_DIR="$CONFIG_DIR/completions" mkdir -p "$COMPLETION_DIR" + # Escape path for shell (handle spaces) + ESCAPED_COMPLETION_DIR=$(printf '%s' "$COMPLETION_DIR" | sed 's/ /\\ /g') + # Detect current shell SHELL_NAME="$(basename "$SHELL" 2>/dev/null || echo "bash")" @@ -152,17 +155,40 @@ install() { zsh) "$BINARY_NAME" completion zsh > "$COMPLETION_DIR/zsh" log_info "Zsh completion installed to $COMPLETION_DIR/zsh" - log_info "Add to your .zshrc: source $COMPLETION_DIR/zsh" + + # Add to .zshrc if not already present + if ! grep -q "menlo/completions/zsh" "$HOME/.zshrc" 2>/dev/null; then + echo "source $ESCAPED_COMPLETION_DIR/zsh" >> "$HOME/.zshrc" + log_info "Added completion to .zshrc" + else + log_info "Completion already in .zshrc" + fi ;; fish) "$BINARY_NAME" completion fish > "$COMPLETION_DIR/fish" log_info "Fish completion installed to $COMPLETION_DIR/fish" - log_info "Add to your config.fish: source $COMPLETION_DIR/fish" + + # Add to config.fish if not already present + FISH_CONFIG="$HOME/.config/fish/config.fish" + mkdir -p "$HOME/.config/fish" + if [ ! -f "$FISH_CONFIG" ] || ! grep -q "menlo/completions/fish" "$FISH_CONFIG" 2>/dev/null; then + echo "source $ESCAPED_COMPLETION_DIR/fish" >> "$FISH_CONFIG" + log_info "Added completion to config.fish" + else + log_info "Completion already in config.fish" + fi ;; bash) "$BINARY_NAME" completion bash > "$COMPLETION_DIR/bash" log_info "Bash completion installed to $COMPLETION_DIR/bash" - log_info "Add to your .bashrc: source $COMPLETION_DIR/bash" + + # Add to .bashrc if not already present + if ! grep -q "menlo/completions/bash" "$HOME/.bashrc" 2>/dev/null; then + echo "source $ESCAPED_COMPLETION_DIR/bash" >> "$HOME/.bashrc" + log_info "Added completion to .bashrc" + else + log_info "Completion already in .bashrc" + fi ;; *) # Install all completions diff --git a/internal/clients/platform/platform.go b/internal/clients/platform/platform.go index ed9a635..dcad80a 100644 --- a/internal/clients/platform/platform.go +++ b/internal/clients/platform/platform.go @@ -192,6 +192,12 @@ func (c *Client) GetRobot(robotID string) (*RobotResponse, error) { return &robot, nil } +// SessionResponse represents the WebRTC session info +type SessionResponse struct { + SFUEndpoint string `json:"sfu_endpoint"` + WebRTCToken string `json:"webrtc_token"` +} + // ValidSemanticCommands are the supported semantic commands var ValidSemanticCommands = []string{ "forward", @@ -217,3 +223,24 @@ func (c *Client) SendSemanticCommand(robotID, command string) error { return nil } + +// CreateSession creates a new session for a robot and returns WebRTC credentials +func (c *Client) CreateSession(robotID string) (*SessionResponse, error) { + resp, err := c.doRequest("POST", "v1/robots/"+robotID+"/session", nil) + if err != nil { + return nil, err + } + defer closeBody(resp) + + var result GeneralResponse[SessionResponse] + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + + var session SessionResponse + if err := result.ParseResult(&session); err != nil { + return nil, err + } + + return &session, nil +} diff --git a/internal/commands/robot.go b/internal/commands/robot.go index 0b0cab3..f16c811 100644 --- a/internal/commands/robot.go +++ b/internal/commands/robot.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "net/url" "github.com/menloresearch/cli/internal/clients/platform" "github.com/menloresearch/cli/internal/config" @@ -145,6 +146,48 @@ Examples: }, } +var robotSessionCmd = &cobra.Command{ + Use: "session", + Short: "Create a WebRTC session for a robot", + Long: `Create a session to connect to a robot via WebRTC. +Returns an SFU endpoint and WebRTC token for connecting. + +Examples: + menlo robot session --robot-id `, + RunE: func(cmd *cobra.Command, args []string) error { + robotID, err := cmd.Flags().GetString("robot-id") + if err != nil { + return err + } + + // If no robot ID provided, try default + if robotID == "" { + cfg, err := config.Load() + if err != nil { + if !config.IsNotExist(err) { + return err + } + } + if cfg != nil { + robotID = cfg.DefaultRobotID + } + } + + // If still no robot ID, ask user to select + if robotID == "" { + robotID, err = selectRobotInteractive() + if err != nil { + return err + } + if robotID == "" { + return nil + } + } + + return createRobotSession(robotID) + }, +} + // selectRobotInteractive prompts user to select a robot from list func selectRobotInteractive() (string, error) { client, err := platform.NewClient() @@ -207,11 +250,43 @@ func sendRobotAction(robotID, action string) error { return nil } +func createRobotSession(robotID string) error { + client, err := platform.NewClient() + if err != nil { + return err + } + + session, err := client.CreateSession(robotID) + if err != nil { + return err + } + + // Generate meet link + meetURL := generateMeetLink(session.SFUEndpoint, session.WebRTCToken) + + fmt.Printf("Session created for robot %s\n\n", robotID) + fmt.Printf("SFU Endpoint: %s\n", session.SFUEndpoint) + fmt.Printf("WebRTC Token: %s\n\n", session.WebRTCToken) + fmt.Printf("Join URL: %s\n", meetURL) + + return nil +} + +func generateMeetLink(sfuEndpoint, token string) string { + baseURL := "https://meet.livekit.io/custom" + params := url.Values{} + params.Set("liveKitUrl", sfuEndpoint) + params.Set("token", token) + return baseURL + "?" + params.Encode() +} + func init() { robotStatusCmd.Flags().String("robot-id", "", "Robot ID") robotActionCmd.Flags().String("robot-id", "", "Robot ID") + robotSessionCmd.Flags().String("robot-id", "", "Robot ID") robotCmd.AddCommand(robotListCmd) robotCmd.AddCommand(robotStatusCmd) robotCmd.AddCommand(robotActionCmd) + robotCmd.AddCommand(robotSessionCmd) rootCmd.AddCommand(robotCmd) } \ No newline at end of file diff --git a/uninstall.sh b/uninstall.sh index 9222299..69693ac 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -77,24 +77,30 @@ uninstall() { log_info "Uninstall complete!" - # Show instructions for shell completions - case "$(uname -s)" in - Darwin*) - CONFIG_DIR="$HOME/Library/Application Support/menlo" - ;; - Linux*) - CONFIG_DIR="$HOME/.config/menlo" - ;; - CYGWIN*|MINGW*) - CONFIG_DIR="$APPDATA/menlo" - ;; - esac + # Remove completion lines from shell rc files + log_info "Removing shell completion lines..." + + # Zsh + if [ -f "$HOME/.zshrc" ]; then + sed -i '' '/menlo\/completions\//d' "$HOME/.zshrc" 2>/dev/null || \ + sed -i '/menlo\/completions\//d' "$HOME/.zshrc" 2>/dev/null || true + log_info "Removed completion from .zshrc" + fi + + # Bash + if [ -f "$HOME/.bashrc" ]; then + sed -i '' '/menlo\/completions\//d' "$HOME/.bashrc" 2>/dev/null || \ + sed -i '/menlo\/completions\//d' "$HOME/.bashrc" 2>/dev/null || true + log_info "Removed completion from .bashrc" + fi - log_info "Shell completions are in: $CONFIG_DIR/completions/" - log_info "Remove these lines from your shell rc file if you added them:" - log_info " source $CONFIG_DIR/completions/zsh # for zsh" - log_info " source $CONFIG_DIR/completions/fish # for fish" - log_info " source $CONFIG_DIR/completions/bash # for bash" + # Fish + FISH_CONFIG="$HOME/.config/fish/config.fish" + if [ -f "$FISH_CONFIG" ]; then + sed -i '' '/menlo\/completions\//d' "$FISH_CONFIG" 2>/dev/null || \ + sed -i '/menlo\/completions\//d' "$FISH_CONFIG" 2>/dev/null || true + log_info "Removed completion from config.fish" + fi } # Check if curl is installed