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.

Informations de source

Dépôt
wavetermdev/waveterm
Dernière activité de la source
19 février 2026 à 20:49
Langue détectée de SKILL.md
anglais
Étoiles
22 482
Forks
1 159

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
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" )
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub