Skip to main content

add-wshcmd

Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.

Jump to install

Source facts

Repository
wavetermdev/waveterm
Last source activity
February 19, 2026 at 20:49
Detected SKILL.md language
English
Stars
22,334
Forks
1,136

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
add-wshcmd
description
Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.
# Adding a New wsh Command to Wave Terminal This guide explains how to add a new command to the `wsh` CLI tool. ## wsh Command System Overview Wave Terminal's `wsh` command provides CLI access to Wave Terminal features. The system uses: 1. **Cobra Framework** - CLI command structure and parsing 2. **Command Files** - Individual command implementations in `cmd/wsh/cmd/wshcmd-*.go` 3. **RPC Client** - Communication with Wave Terminal backend via `RpcClient` 4. **Activity Tracking** - Telemetry for command usage analytics 5. **Documentation** - User-facing docs in `docs/docs/wsh-reference.mdx` Commands are registered in their `init()` functions and execute through the Cobra framework. ## Step-by-Step Guide ### Step 1: Create Command File Create a new file in `cmd/wsh/cmd/` named `wshcmd-[commandname].go`: ```go // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 package cmd import ( "fmt" "github.com/spf13/cobra" "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" ) var myCommandCmd = &cobra.Command{ Use: "mycommand [args]", Short: "Brief description of what this command does", Long: `Detailed description of the command. Can include multiple lines and examples of usage.`, RunE: myCommandRun, PreRunE: preRunSetupRpcClient, // Include if command needs RPC DisableFlagsInUseLine: true, } // Flag variables var ( myCommandFlagExample string myCommandFlagVerbose bool ) func init() { // Add command to root rootCmd.AddCommand(myCommandCmd) // Define flags myCommandCmd.Flags().StringVarP(&myCommandFlagExample, "example", "e", "", "example flag description") myCommandCmd.Flags().BoolVarP(&myCommandFlagVerbose, "verbose", "v", false, "enable verbose output") } func myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) { // Always track activity for telemetry defer func() { sendActivity("mycommand", rtnErr == nil) }() // Validate arguments if len(args) == 0 { OutputHelpMessage(cmd) return fmt.Errorf("requires at least one argument") } // Command implementation fmt.Printf("Command executed successfully\n") return nil } ``` **File Naming Convention:** - Use `wshcmd-[commandname].go` format - Use lowercase, hyphenated names for multi-word commands - Examples: `wshcmd-getvar.go`, `wshcmd-setmeta.go`, `wshcmd-ai.go` ### Step 2: Command Structure #### Basic Command Structure ```go var myCommandCmd = &cobra.Command{ Use: "mycommand [required] [optional...]", Short: "One-line description (shown in help)", Long: `Detailed multi-line description`, // Argument validation Args: cobra.MinimumNArgs(1), // Or cobra.ExactArgs(1), cobra.NoArgs, etc. // Execution function RunE: myCommandRun, // Pre-execution setup (if needed) PreRunE: preRunSetupRpcClient, // Sets up RPC client for backend communication // Example usage (optional) Example: " wsh mycommand foo\n wsh mycommand --flag bar", // Disable flag notation in usage line DisableFlagsInUseLine: true, } ``` **Key Fields:** - `Use`: Command name and argument pattern - `Short`: Brief description for command list - `Long`: Detailed description shown in help - `Args`: Argument validator (optional) - `RunE`: Main execution function (returns error) - `PreRunE`: Setup function that runs before `RunE` - `Example`: Usage examples (optional) - `DisableFlagsInUseLine`: Clean up help display #### When to Use PreRunE Include `PreRunE: preRunSetupRpcClient` if your command: - Communicates with the Wave Terminal backend - Needs access to `RpcClient` - Requires JWT authentication (WAVETERM_JWT env var) - Makes RPC calls via `wshclient.*Command()` functions **Don't include PreRunE** for commands that: - Only manipulate local state - Don't need backend communication - Are purely informational/local operations ### Step 3: Implement Command Logic #### Command Function Pattern ```go func myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) { // Step 1: Always track activity (for telemetry) defer func() { sendActivity("mycommand", rtnErr == nil) }() // Step 2: Validate arguments and flags if len(args) != 1 { OutputHelpMessage(cmd) return fmt.Errorf("requires exactly one argument") } // Step 3: Parse/prepare data targetArg := args[0] // Step 4: Make RPC call if needed result, err := wshclient.SomeCommand(RpcClient, wshrpc.CommandSomeData{ Field: targetArg, }, &wshrpc.RpcOpts{Timeout: 2000}) if err != nil { return fmt.Errorf("executing command: %w", err) } // Step 5: Output results fmt.Printf("Result: %s\n", result) return nil } ``` **Important Patterns:** 1. **Activity Tracking**: Always include deferred `sendActivity()` call ```go defer func() { sendActivity("commandname", rtnErr == nil) }() ``` 2. **Error Handling**: Return errors, don't call `os.Exit()` ```go if err != nil { return fmt.Errorf("context: %w", err) } ``` 3. **Output**: Use standard `fmt` package for output ```go fmt.Printf("Success message\n") fmt.Fprintf(os.Stderr, "Error message\n") ``` 4. **Help Messages**: Show help when arguments are invalid ```go if len(args) == 0 { OutputHelpMessage(cmd) return fmt.Errorf("requires arguments") } ``` 5. **Exit Codes**: Set custom exit code via `WshExitCode` ```go if notFound { WshExitCode = 1 return nil // Don't return error, just set exit code } ``` ### Step 4: Define Flags Add flags in the `init()` function: ```go var ( // Declare flag variables at package level myCommandFlagString string myCommandFlagBool bool myCommandFlagInt int ) func init() { rootCmd.AddCommand(myCommandCmd) // String flag with short version myCommandCmd.Flags().StringVarP(&myCommandFlagString, "name", "n", "default", "description") // Boolean flag myCommandCmd.Flags().BoolVarP(&myCommandFlagBool, "verbose", "v", false, "enable verbose") // Integer flag myCommandCmd.Flags().IntVar(&myCommandFlagInt, "count", 10, "set count") // Flag without short version myCommandCmd.Flags().StringVar(&myCommandFlagString, "longname", "", "description") } ``` **Flag Types:** - `StringVar/StringVarP` - String values - `BoolVar/BoolVarP` - Boolean flags - `IntVar/IntVarP` - Integer values - The `P` suffix versions include a short flag name **Flag Naming:** - Use camelCase for variable names: `myCommandFlagName` - Use kebab-case for flag names: `--flag-name` - Prefix variable names with command name for clarity ### Step 5: Working with Block Arguments Many commands operate on blocks. Use the standard block resolution pattern: ```go func myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) { defer func() { sendActivity("mycommand", rtnErr == nil) }() // Resolve block using the -b/--block flag fullORef, err := resolveBlockArg() if err != nil { return err } // Use the blockid in RPC call err = wshclient.SomeCommand(RpcClient, wshrpc.CommandSomeData{ BlockId: fullORef.OID, }, &wshrpc.RpcOpts{Timeout: 2000}) if err != nil { return fmt.Errorf("command failed: %w", err) } return nil } ``` **Block Resolution:** - The `-b/--block` flag is defined globally in `wshcmd-root.go` - `resolveBlockArg()` resolves the block argument to a full ORef - Supports: `this`, `tab`, full UUIDs, 8-char prefixes, block numbers - Default is `"this"` (current block) **Alternative: Manual Block Resolution** ```go // Get tab ID from environment tabId := os.Getenv("WAVETERM_TABID") if tabId == "" { return fmt.Errorf("WAVETERM_TABID not set") } // Create route for tab-level operations route := wshutil.MakeTabRouteId(tabId) // Use route in RPC call err := wshclient.SomeCommand(RpcClient, commandData, &wshrpc.RpcOpts{ Route: route, Timeout: 2000, }) ``` ### Step 6: Making RPC Calls Use the `wshclient` package to make RPC calls: ```go import ( "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" ) // Simple RPC call result, err := wshclient.GetMetaCommand(RpcClient, wshrpc.CommandGetMetaData{ ORef: *fullORef, }, &wshrpc.RpcOpts{Timeout: 2000}) if err != nil { return fmt.Errorf("getting metadata: %w", err) } // RPC call with routing err := wshclient.SetMetaCommand(RpcClient, wshrpc.CommandSetMetaData{ ORef: *fullORef, Meta: metaMap, }, &wshrpc.RpcOpts{ Route: route, Timeout: 5000, }) if err != nil { return fmt.Errorf("setting metadata: %w", err) } ``` **RPC Options:** - `Timeout`: Request timeout in milliseconds (typically 2000-5000) - `Route`: Route ID for targeting specific components - Available routes: `wshutil.ControlRoute`, `wshutil.MakeTabRouteId(tabId)` ### Step 7: Add Documentation Add your command to `docs/docs/wsh-reference.mdx`: ````markdown ## mycommand Brief description of what the command does. ```sh wsh mycommand [args] [flags] ``` Detailed explanation of the command's purpose and behavior. Flags: - `-n, --name <value>` - description of this flag - `-v, --verbose` - enable verbose output - `-b, --block <blockid>` - specify target block (default: current block) Examples: ```sh # Basic usage wsh mycommand arg1 # With flags wsh mycommand --name value arg1 # With block targeting wsh mycommand -b 2 arg1 # Complex example wsh mycommand -v --name "example" arg1 arg2 ``` Additional notes, tips, or warnings about the command. --- ```` **Documentation Guidelines:** - Place in alphabetical order with other commands - Include command signature with argument pattern - List all flags with short and long versions - Provide practical examples (at least 3-5) - Explain common use cases and patterns - Add tips or warnings if relevant - Use `---` separator between commands ### Step 8: Test Your Command Build and test the command: ```bash # Build wsh task build:wsh # Or build everything task build # Test the command ./bin/wsh/wsh mycommand --help ./bin/wsh/wsh mycommand arg1 arg2 ``` **Testing Checklist:** - [ ] Help message displays correctly - [ ] Required arguments validated - [ ] Flags work as expected - [ ] Error messages are clear - [ ] Success cases work correctly - [ ] RPC calls complete successfully - [ ] Output is formatted correctly ## Complete Examples ### Example 1: Simple Command with No RPC **Use case:** A command that prints Wave Terminal version info #### Command File (`cmd/wsh/cmd/wshcmd-version.go`) ```go // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 package cmd import ( "github.com/spf13/cobra" "github.com/wavetermdev/waveterm/pkg/wavebase" ) var versionCmd = &cobra.Command{ Use: "version", Short: "Print Wave Terminal version", RunE: versionRun, } func init() { rootCmd.AddCommand(versionCmd) } func versionRun(cmd *cobra.Command, args []string) (rtnErr error) { defer func() { sendActivity("version", rtnErr == nil) }() fmt.Printf("Wave Terminal %s\n", wavebase.WaveVersion) return nil } ``` #### Documentation ````markdown ## version Print the current Wave Terminal version. ```sh wsh version ``` Examples: ```sh # Print version wsh version ``` ```` ### Example 2: Command with Flags and RPC **Use case:** A command to update block title #### Command File (`cmd/wsh/cmd/wshcmd-settitle.go`) ```go // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 package cmd import ( "fmt" "github.com/spf13/cobra" "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" )
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub