cremote/main.go

513 lines
18 KiB
Go

package main
import (
"flag"
"fmt"
"os"
"sort"
"git.teamworkapps.com/shortcut/cremote/client"
)
var (
// Global flags
daemonHost = flag.String("host", "localhost", "Daemon host")
daemonPort = flag.Int("port", 8989, "Daemon port")
)
func main() {
// Define subcommands
openTabCmd := flag.NewFlagSet("open-tab", flag.ExitOnError)
loadURLCmd := flag.NewFlagSet("load-url", flag.ExitOnError)
fillFormCmd := flag.NewFlagSet("fill-form", flag.ExitOnError)
uploadFileCmd := flag.NewFlagSet("upload-file", flag.ExitOnError)
submitFormCmd := flag.NewFlagSet("submit-form", flag.ExitOnError)
getSourceCmd := flag.NewFlagSet("get-source", flag.ExitOnError)
getElementCmd := flag.NewFlagSet("get-element", flag.ExitOnError)
clickElementCmd := flag.NewFlagSet("click-element", flag.ExitOnError)
closeTabCmd := flag.NewFlagSet("close-tab", flag.ExitOnError)
waitNavCmd := flag.NewFlagSet("wait-navigation", flag.ExitOnError)
evalJsCmd := flag.NewFlagSet("eval-js", flag.ExitOnError)
switchIframeCmd := flag.NewFlagSet("switch-iframe", flag.ExitOnError)
switchMainCmd := flag.NewFlagSet("switch-main", flag.ExitOnError)
screenshotCmd := flag.NewFlagSet("screenshot", flag.ExitOnError)
statusCmd := flag.NewFlagSet("status", flag.ExitOnError)
listTabsCmd := flag.NewFlagSet("list-tabs", flag.ExitOnError)
// Define flags for each subcommand
// open-tab flags
openTabTimeout := openTabCmd.Int("timeout", 5, "Timeout in seconds for opening the tab")
openTabHost := openTabCmd.String("host", "localhost", "Daemon host")
openTabPort := openTabCmd.Int("port", 8989, "Daemon port")
// load-url flags
loadURLTabID := loadURLCmd.String("tab", "", "Tab ID to load URL in (optional, uses current tab if not specified)")
loadURLTarget := loadURLCmd.String("url", "", "URL to load")
loadURLTimeout := loadURLCmd.Int("timeout", 5, "Timeout in seconds for loading the URL")
loadURLHost := loadURLCmd.String("host", "localhost", "Daemon host")
loadURLPort := loadURLCmd.Int("port", 8989, "Daemon port")
// fill-form flags
fillFormTabID := fillFormCmd.String("tab", "", "Tab ID to fill form in (optional, uses current tab if not specified)")
fillFormSelector := fillFormCmd.String("selector", "", "CSS selector for the input field")
fillFormValue := fillFormCmd.String("value", "", "Value to fill in the form field")
fillFormSelectionTimeout := fillFormCmd.Int("selection-timeout", 5, "Timeout in seconds for finding the element")
fillFormActionTimeout := fillFormCmd.Int("action-timeout", 5, "Timeout in seconds for the fill action")
fillFormHost := fillFormCmd.String("host", "localhost", "Daemon host")
fillFormPort := fillFormCmd.Int("port", 8989, "Daemon port")
// upload-file flags
uploadFileTabID := uploadFileCmd.String("tab", "", "Tab ID to upload file in (optional, uses current tab if not specified)")
uploadFileSelector := uploadFileCmd.String("selector", "", "CSS selector for the file input")
uploadFilePath := uploadFileCmd.String("file", "", "Path to the file to upload")
uploadFileSelectionTimeout := uploadFileCmd.Int("selection-timeout", 5, "Timeout in seconds for finding the element")
uploadFileActionTimeout := uploadFileCmd.Int("action-timeout", 5, "Timeout in seconds for the upload action")
uploadFileHost := uploadFileCmd.String("host", "localhost", "Daemon host")
uploadFilePort := uploadFileCmd.Int("port", 8989, "Daemon port")
// submit-form flags
submitFormTabID := submitFormCmd.String("tab", "", "Tab ID to submit form in (optional, uses current tab if not specified)")
submitFormSelector := submitFormCmd.String("selector", "", "CSS selector for the form")
submitFormSelectionTimeout := submitFormCmd.Int("selection-timeout", 5, "Timeout in seconds for finding the element")
submitFormActionTimeout := submitFormCmd.Int("action-timeout", 5, "Timeout in seconds for the submit action")
submitFormHost := submitFormCmd.String("host", "localhost", "Daemon host")
submitFormPort := submitFormCmd.Int("port", 8989, "Daemon port")
// get-source flags
getSourceTabID := getSourceCmd.String("tab", "", "Tab ID to get source from (optional, uses current tab if not specified)")
getSourceTimeout := getSourceCmd.Int("timeout", 5, "Timeout in seconds for getting the page source")
getSourceHost := getSourceCmd.String("host", "localhost", "Daemon host")
getSourcePort := getSourceCmd.Int("port", 8989, "Daemon port")
// get-element flags
getElementTabID := getElementCmd.String("tab", "", "Tab ID to get element from (optional, uses current tab if not specified)")
getElementSelector := getElementCmd.String("selector", "", "CSS selector for the element")
getElementSelectionTimeout := getElementCmd.Int("selection-timeout", 5, "Timeout in seconds for finding the element")
getElementHost := getElementCmd.String("host", "localhost", "Daemon host")
getElementPort := getElementCmd.Int("port", 8989, "Daemon port")
// click-element flags
clickElementTabID := clickElementCmd.String("tab", "", "Tab ID to click element in (optional, uses current tab if not specified)")
clickElementSelector := clickElementCmd.String("selector", "", "CSS selector for the element to click")
clickElementSelectionTimeout := clickElementCmd.Int("selection-timeout", 5, "Timeout in seconds for finding the element")
clickElementActionTimeout := clickElementCmd.Int("action-timeout", 5, "Timeout in seconds for the click action")
clickElementHost := clickElementCmd.String("host", "localhost", "Daemon host")
clickElementPort := clickElementCmd.Int("port", 8989, "Daemon port")
// close-tab flags
closeTabID := closeTabCmd.String("tab", "", "Tab ID to close (optional, uses current tab if not specified)")
closeTabTimeout := closeTabCmd.Int("timeout", 5, "Timeout in seconds for closing the tab")
closeTabHost := closeTabCmd.String("host", "localhost", "Daemon host")
closeTabPort := closeTabCmd.Int("port", 8989, "Daemon port")
// wait-navigation flags
waitNavTabID := waitNavCmd.String("tab", "", "Tab ID to wait for navigation (optional, uses current tab if not specified)")
waitNavTimeout := waitNavCmd.Int("timeout", 5, "Timeout in seconds")
waitNavHost := waitNavCmd.String("host", "localhost", "Daemon host")
waitNavPort := waitNavCmd.Int("port", 8989, "Daemon port")
// eval-js flags
evalJsTabID := evalJsCmd.String("tab", "", "Tab ID to execute JavaScript in (optional, uses current tab if not specified)")
evalJsCode := evalJsCmd.String("code", "", "JavaScript code to execute")
evalJsTimeout := evalJsCmd.Int("timeout", 5, "Timeout in seconds for JavaScript execution")
evalJsHost := evalJsCmd.String("host", "localhost", "Daemon host")
evalJsPort := evalJsCmd.Int("port", 8989, "Daemon port")
// switch-iframe flags
switchIframeTabID := switchIframeCmd.String("tab", "", "Tab ID to switch iframe context in (optional, uses current tab if not specified)")
switchIframeSelector := switchIframeCmd.String("selector", "", "CSS selector for the iframe element")
switchIframeTimeout := switchIframeCmd.Int("timeout", 5, "Timeout in seconds for iframe switching")
switchIframeHost := switchIframeCmd.String("host", "localhost", "Daemon host")
switchIframePort := switchIframeCmd.Int("port", 8989, "Daemon port")
// switch-main flags
switchMainTabID := switchMainCmd.String("tab", "", "Tab ID to switch back to main context (optional, uses current tab if not specified)")
switchMainHost := switchMainCmd.String("host", "localhost", "Daemon host")
switchMainPort := switchMainCmd.Int("port", 8989, "Daemon port")
// screenshot flags
screenshotTabID := screenshotCmd.String("tab", "", "Tab ID to take screenshot of (optional, uses current tab if not specified)")
screenshotOutput := screenshotCmd.String("output", "", "Output file path for the screenshot (PNG format)")
screenshotFullPage := screenshotCmd.Bool("full-page", false, "Capture full page screenshot (default: viewport only)")
screenshotTimeout := screenshotCmd.Int("timeout", 5, "Timeout in seconds for taking the screenshot")
screenshotHost := screenshotCmd.String("host", "localhost", "Daemon host")
screenshotPort := screenshotCmd.Int("port", 8989, "Daemon port")
// status flags
statusHost := statusCmd.String("host", "localhost", "Daemon host")
statusPort := statusCmd.Int("port", 8989, "Daemon port")
// list-tabs flags
listTabsHost := listTabsCmd.String("host", "localhost", "Daemon host")
listTabsPort := listTabsCmd.Int("port", 8989, "Daemon port")
// Check if a subcommand is provided
if len(os.Args) < 2 {
printUsage()
os.Exit(1)
}
// Parse the appropriate subcommand
switch os.Args[1] {
case "open-tab":
openTabCmd.Parse(os.Args[2:])
// Create a client
c := client.NewClient(*openTabHost, *openTabPort)
// Open a new tab
tabID, err := c.OpenTab(*openTabTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
// Print the tab ID
fmt.Println(tabID)
case "load-url":
loadURLCmd.Parse(os.Args[2:])
if *loadURLTarget == "" {
fmt.Println("Error: url flag is required")
loadURLCmd.PrintDefaults()
os.Exit(1)
}
// Create a client
c := client.NewClient(*loadURLHost, *loadURLPort)
// Load the URL
err := c.LoadURL(*loadURLTabID, *loadURLTarget, *loadURLTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("URL loaded successfully")
case "fill-form":
fillFormCmd.Parse(os.Args[2:])
if *fillFormSelector == "" || *fillFormValue == "" {
fmt.Println("Error: selector and value flags are required")
fillFormCmd.PrintDefaults()
os.Exit(1)
}
// Create a client
c := client.NewClient(*fillFormHost, *fillFormPort)
// Fill the form field
err := c.FillFormField(*fillFormTabID, *fillFormSelector, *fillFormValue, *fillFormSelectionTimeout, *fillFormActionTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("Form field filled successfully")
case "upload-file":
uploadFileCmd.Parse(os.Args[2:])
if *uploadFileSelector == "" || *uploadFilePath == "" {
fmt.Println("Error: selector and file flags are required")
uploadFileCmd.PrintDefaults()
os.Exit(1)
}
// Create a client
c := client.NewClient(*uploadFileHost, *uploadFilePort)
// Check if the local file exists
if _, err := os.Stat(*uploadFilePath); os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Error: Local file does not exist: %s\n", *uploadFilePath)
os.Exit(1)
}
// First, transfer the file to the daemon container
fmt.Printf("Transferring file to daemon container: %s\n", *uploadFilePath)
containerPath, err := c.UploadFileToContainer(*uploadFilePath, "")
if err != nil {
fmt.Fprintf(os.Stderr, "Error transferring file to container: %v\n", err)
os.Exit(1)
}
fmt.Printf("File transferred to container: %s\n", containerPath)
// Then upload the file to the web form using the container path
err = c.UploadFile(*uploadFileTabID, *uploadFileSelector, containerPath, *uploadFileSelectionTimeout, *uploadFileActionTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error uploading file to web form: %v\n", err)
os.Exit(1)
}
fmt.Println("File uploaded to web form successfully")
case "submit-form":
submitFormCmd.Parse(os.Args[2:])
if *submitFormSelector == "" {
fmt.Println("Error: selector flag is required")
submitFormCmd.PrintDefaults()
os.Exit(1)
}
// Create a client
c := client.NewClient(*submitFormHost, *submitFormPort)
// Submit the form
err := c.SubmitForm(*submitFormTabID, *submitFormSelector, *submitFormSelectionTimeout, *submitFormActionTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("Form submitted successfully")
case "get-source":
getSourceCmd.Parse(os.Args[2:])
// Create a client
c := client.NewClient(*getSourceHost, *getSourcePort)
// Get the page source
source, err := c.GetPageSource(*getSourceTabID, *getSourceTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
// Print the source
fmt.Println(source)
case "get-element":
getElementCmd.Parse(os.Args[2:])
if *getElementSelector == "" {
fmt.Println("Error: selector flag is required")
getElementCmd.PrintDefaults()
os.Exit(1)
}
// Create a client
c := client.NewClient(*getElementHost, *getElementPort)
// Get the element HTML
html, err := c.GetElementHTML(*getElementTabID, *getElementSelector, *getElementSelectionTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
// Print the HTML
fmt.Println(html)
case "click-element":
clickElementCmd.Parse(os.Args[2:])
if *clickElementSelector == "" {
fmt.Println("Error: selector flag is required")
clickElementCmd.PrintDefaults()
os.Exit(1)
}
// Create a client
c := client.NewClient(*clickElementHost, *clickElementPort)
// Click the element
err := c.ClickElement(*clickElementTabID, *clickElementSelector, *clickElementSelectionTimeout, *clickElementActionTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("Element clicked successfully")
case "close-tab":
closeTabCmd.Parse(os.Args[2:])
// Create a client
c := client.NewClient(*closeTabHost, *closeTabPort)
// Close the tab
err := c.CloseTab(*closeTabID, *closeTabTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("Tab closed successfully")
case "wait-navigation":
waitNavCmd.Parse(os.Args[2:])
// Create a client
c := client.NewClient(*waitNavHost, *waitNavPort)
// Wait for navigation
err := c.WaitNavigation(*waitNavTabID, *waitNavTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("Navigation completed")
case "eval-js":
evalJsCmd.Parse(os.Args[2:])
if *evalJsCode == "" {
fmt.Println("Error: code flag is required")
evalJsCmd.PrintDefaults()
os.Exit(1)
}
// Create a client
c := client.NewClient(*evalJsHost, *evalJsPort)
// Execute JavaScript
result, err := c.EvalJS(*evalJsTabID, *evalJsCode, *evalJsTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
// Print the result if there is one
if result != "" {
fmt.Println(result)
}
case "switch-iframe":
switchIframeCmd.Parse(os.Args[2:])
if *switchIframeSelector == "" {
fmt.Println("Error: selector flag is required")
switchIframeCmd.PrintDefaults()
os.Exit(1)
}
// Create a client
c := client.NewClient(*switchIframeHost, *switchIframePort)
// Switch to iframe
err := c.SwitchToIframe(*switchIframeTabID, *switchIframeSelector, *switchIframeTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("Switched to iframe context")
case "switch-main":
switchMainCmd.Parse(os.Args[2:])
// Create a client
c := client.NewClient(*switchMainHost, *switchMainPort)
// Switch back to main page
err := c.SwitchToMain(*switchMainTabID)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("Switched to main page context")
case "screenshot":
screenshotCmd.Parse(os.Args[2:])
if *screenshotOutput == "" {
fmt.Println("Error: output flag is required")
screenshotCmd.PrintDefaults()
os.Exit(1)
}
// Create a client
c := client.NewClient(*screenshotHost, *screenshotPort)
// Take screenshot
err := c.TakeScreenshot(*screenshotTabID, *screenshotOutput, *screenshotFullPage, *screenshotTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Screenshot saved to: %s\n", *screenshotOutput)
case "status":
statusCmd.Parse(os.Args[2:])
// Create a client
c := client.NewClient(*statusHost, *statusPort)
// Check daemon status
running, err := c.CheckStatus()
if err != nil {
// If we can't connect, the daemon is not running
fmt.Println("Daemon is not running")
os.Exit(0)
}
if running {
fmt.Println("Daemon is running")
} else {
fmt.Println("Daemon is not running")
os.Exit(0)
}
case "list-tabs":
listTabsCmd.Parse(os.Args[2:])
// Create a client
c := client.NewClient(*listTabsHost, *listTabsPort)
// List tabs
tabs, err := c.ListTabs()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
// Sort tabs by history index (most recent first)
sort.Slice(tabs, func(i, j int) bool {
return tabs[i].HistoryIndex > tabs[j].HistoryIndex
})
// Print the tabs
if len(tabs) == 0 {
fmt.Println("No tabs open")
} else {
fmt.Println("Open tabs (in order of recent use):")
for _, tab := range tabs {
currentMarker := " "
if tab.IsCurrent {
currentMarker = "*"
}
fmt.Printf("%s %s: %s\n", currentMarker, tab.ID, tab.URL)
}
}
default:
printUsage()
os.Exit(1)
}
}
func printUsage() {
fmt.Println("Usage: cremote <command> [options]")
fmt.Println("Commands:")
fmt.Println(" open-tab Open a new tab and return its ID")
fmt.Println(" load-url Load a URL in a tab")
fmt.Println(" fill-form Fill a form field with a value")
fmt.Println(" upload-file Transfer a file to daemon container and upload to a file input")
fmt.Println(" submit-form Submit a form")
fmt.Println(" get-source Get the source code of a page")
fmt.Println(" get-element Get the HTML of an element")
fmt.Println(" click-element Click on an element")
fmt.Println(" close-tab Close a tab")
fmt.Println(" wait-navigation Wait for a navigation event")
fmt.Println(" eval-js Execute JavaScript code in a tab")
fmt.Println(" switch-iframe Switch to iframe context for subsequent commands")
fmt.Println(" switch-main Switch back to main page context")
fmt.Println(" screenshot Take a screenshot of the current page")
fmt.Println(" list-tabs List all open tabs")
fmt.Println(" status Check if the daemon is running")
fmt.Println("\nRun 'cremote <command> -h' for more information on a command")
fmt.Println("\nBefore using this tool, make sure the daemon is running:")
fmt.Println(" cremotedaemon")
fmt.Println("\nThe daemon requires Chromium/Chrome to be running with remote debugging enabled:")
fmt.Println(" chromium --remote-debugging-port=9222 --user-data-dir=/tmp/chromium-debug")
fmt.Println("\nNote: Most commands can use the current tab if you don't specify a tab ID.")
}