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.

소스 정보

저장소
wavetermdev/waveterm
최근 소스 활동
2026년 2월 19일 20:49
감지된 SKILL.md 언어
영어
스타
22,482
포크
1,159

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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" )
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기