Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
```

Expand Down Expand Up @@ -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 <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.
Expand Down Expand Up @@ -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
```
34 changes: 30 additions & 4 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -141,28 +141,54 @@ 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")"

case "$SHELL_NAME" in
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
Expand Down
27 changes: 27 additions & 0 deletions internal/clients/platform/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
}
75 changes: 75 additions & 0 deletions internal/commands/robot.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"fmt"
"net/url"

"github.com/menloresearch/cli/internal/clients/platform"
"github.com/menloresearch/cli/internal/config"
Expand Down Expand Up @@ -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 <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()
Expand Down Expand Up @@ -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)
}
40 changes: 23 additions & 17 deletions uninstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading