package main import ( "flag" "fmt" "os" "sort" "git.teamworkapps.com/shortcut/cremote/client" ) const Version = "2.0.0" 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") fillFormTimeout := fillFormCmd.Int("timeout", 5, "Timeout in seconds for the fill operation") 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") uploadFileTimeout := uploadFileCmd.Int("timeout", 5, "Timeout in seconds for the upload operation") 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") submitFormTimeout := submitFormCmd.Int("timeout", 5, "Timeout in seconds for the submit operation") 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") getElementTimeout := getElementCmd.Int("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") clickElementTimeout := clickElementCmd.Int("timeout", 5, "Timeout in seconds for the click operation") 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)") switchMainTimeout := switchMainCmd.Int("timeout", 5, "Timeout in seconds for switching back to main context") 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 "version": fmt.Printf("cremote CLI version %s\n", Version) // Also get daemon version if possible c := client.NewClient("localhost", 8989) daemonVersion, err := c.GetVersion() if err != nil { fmt.Printf("Daemon: Unable to connect (%v)\n", err) } else { fmt.Printf("Daemon version %s\n", daemonVersion) } return 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, *fillFormTimeout) 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, *uploadFileTimeout) 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, *submitFormTimeout) 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, *getElementTimeout) 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, *clickElementTimeout) 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, *switchMainTimeout) 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 [options]") fmt.Println("Commands:") fmt.Println(" version Show version information for CLI and daemon") 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 -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.") }