Loading...
Loading...
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.
npx skill4agent add wavetermdev/waveterm add-wshcmdwshwshcmd/wsh/cmd/wshcmd-*.goRpcClientdocs/docs/wsh-reference.mdxinit()cmd/wsh/cmd/wshcmd-[commandname].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
}wshcmd-[commandname].gowshcmd-getvar.gowshcmd-setmeta.gowshcmd-ai.govar 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,
}UseShortLongArgsRunEPreRunERunEExampleDisableFlagsInUseLinePreRunE: preRunSetupRpcClientRpcClientwshclient.*Command()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
}sendActivity()defer func() {
sendActivity("commandname", rtnErr == nil)
}()os.Exit()if err != nil {
return fmt.Errorf("context: %w", err)
}fmtfmt.Printf("Success message\n")
fmt.Fprintf(os.Stderr, "Error message\n")if len(args) == 0 {
OutputHelpMessage(cmd)
return fmt.Errorf("requires arguments")
}WshExitCodeif notFound {
WshExitCode = 1
return nil // Don't return error, just set exit code
}init()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")
}StringVar/StringVarPBoolVar/BoolVarPIntVar/IntVarPPmyCommandFlagName--flag-namefunc 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
}-b/--blockwshcmd-root.goresolveBlockArg()thistab"this"// 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,
})wshclientimport (
"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)
}TimeoutRoutewshutil.ControlRoutewshutil.MakeTabRouteId(tabId)docs/docs/wsh-reference.mdx## 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.
------# Build wsh
task build:wsh
# Or build everything
task build
# Test the command
./bin/wsh/wsh mycommand --help
./bin/wsh/wsh mycommand arg1 arg2cmd/wsh/cmd/wshcmd-version.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
}## version
Print the current Wave Terminal version.
```sh
wsh version
```
Examples:
```sh
# Print version
wsh version
```cmd/wsh/cmd/wshcmd-settitle.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 setTitleCmd = &cobra.Command{
Use: "settitle [title]",
Short: "Set block title",
Long: `Set the title for the current or specified block.`,
Args: cobra.ExactArgs(1),
RunE: setTitleRun,
PreRunE: preRunSetupRpcClient,
DisableFlagsInUseLine: true,
}
var setTitleIcon string
func init() {
rootCmd.AddCommand(setTitleCmd)
setTitleCmd.Flags().StringVarP(&setTitleIcon, "icon", "i", "", "set block icon")
}
func setTitleRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
sendActivity("settitle", rtnErr == nil)
}()
title := args[0]
// Resolve block
fullORef, err := resolveBlockArg()
if err != nil {
return err
}
// Build metadata map
meta := make(map[string]interface{})
meta["title"] = title
if setTitleIcon != "" {
meta["icon"] = setTitleIcon
}
// Make RPC call
err = wshclient.SetMetaCommand(RpcClient, wshrpc.CommandSetMetaData{
ORef: *fullORef,
Meta: meta,
}, &wshrpc.RpcOpts{Timeout: 2000})
if err != nil {
return fmt.Errorf("setting title: %w", err)
}
fmt.Printf("title updated\n")
return nil
}## settitle
Set the title for a block.
```sh
wsh settitle [title]
```
Update the display title for the current or specified block. Optionally set an icon as well.
Flags:
- `-i, --icon <icon>` - set block icon along with title
- `-b, --block <blockid>` - specify target block (default: current block)
Examples:
```sh
# Set title for current block
wsh settitle "My Terminal"
# Set title and icon
wsh settitle --icon "terminal" "Development Shell"
# Set title for specific block
wsh settitle -b 2 "Build Output"
```wsh conncmd/wsh/cmd/wshcmd-mygroup.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 myGroupCmd = &cobra.Command{
Use: "mygroup",
Short: "Manage something",
}
var myGroupListCmd = &cobra.Command{
Use: "list",
Short: "List items",
RunE: myGroupListRun,
PreRunE: preRunSetupRpcClient,
}
var myGroupAddCmd = &cobra.Command{
Use: "add [name]",
Short: "Add an item",
Args: cobra.ExactArgs(1),
RunE: myGroupAddRun,
PreRunE: preRunSetupRpcClient,
}
func init() {
// Add parent command
rootCmd.AddCommand(myGroupCmd)
// Add subcommands
myGroupCmd.AddCommand(myGroupListCmd)
myGroupCmd.AddCommand(myGroupAddCmd)
}
func myGroupListRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
sendActivity("mygroup:list", rtnErr == nil)
}()
// Implementation
fmt.Printf("Listing items...\n")
return nil
}
func myGroupAddRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
sendActivity("mygroup:add", rtnErr == nil)
}()
name := args[0]
fmt.Printf("Adding item: %s\n", name)
return nil
}## mygroup
Manage something with subcommands.
### list
List all items.
```sh
wsh mygroup list
```
### add
Add a new item.
```sh
wsh mygroup add [name]
```
Examples:
```sh
# List items
wsh mygroup list
# Add an item
wsh mygroup add "new-item"
```import "io"
func myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
sendActivity("mycommand", rtnErr == nil)
}()
// Check if reading from stdin (using "-" convention)
var data []byte
var err error
if len(args) > 0 && args[0] == "-" {
data, err = io.ReadAll(os.Stdin)
if err != nil {
return fmt.Errorf("reading stdin: %w", err)
}
} else {
// Read from file or other source
data, err = os.ReadFile(args[0])
if err != nil {
return fmt.Errorf("reading file: %w", err)
}
}
// Process data
fmt.Printf("Read %d bytes\n", len(data))
return nil
}import (
"encoding/json"
"io"
)
func loadJSONFile(filepath string) (map[string]interface{}, error) {
var data []byte
var err error
if filepath == "-" {
data, err = io.ReadAll(os.Stdin)
if err != nil {
return nil, fmt.Errorf("reading stdin: %w", err)
}
} else {
data, err = os.ReadFile(filepath)
if err != nil {
return nil, fmt.Errorf("reading file: %w", err)
}
}
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
return nil, fmt.Errorf("parsing JSON: %w", err)
}
return result, nil
}func myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
sendActivity("mycommand", rtnErr == nil)
}()
isTty := getIsTty()
// Output value
fmt.Printf("%s", value)
// Add newline only if TTY (for better piping experience)
if isTty {
fmt.Printf("\n")
}
return nil
}func myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
sendActivity("mycommand", rtnErr == nil)
}()
// Get block ID from environment
blockId := os.Getenv("WAVETERM_BLOCKID")
if blockId == "" {
return fmt.Errorf("WAVETERM_BLOCKID not set")
}
// Get tab ID from environment
tabId := os.Getenv("WAVETERM_TABID")
if tabId == "" {
return fmt.Errorf("WAVETERM_TABID not set")
}
fmt.Printf("Block: %s, Tab: %s\n", blockId, tabId)
return nil
}fmt.Errorf("context: %w", err)os.Exit()panic()WshExitCode-vfmt.Fprintf(os.Stderr, ...)-xsendActivity()defer func() {
sendActivity("commandname", rtnErr == nil)
}()// Bad
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
// Good
if err != nil {
return fmt.Errorf("operation failed: %w", err)
}if len(args) == 0 {
OutputHelpMessage(cmd)
return fmt.Errorf("requires at least one argument")
}init()func init() {
rootCmd.AddCommand(myCommandCmd)
}fmt// For stdout
fmt.Printf("output\n")
// For stderr
fmt.Fprintf(os.Stderr, "error message\n")cmd/wsh/cmd/wshcmd-[commandname].goPreRunE: preRunSetupRpcClientrootCmdinit()init()docs/docs/wsh-reference.mdxtask build:wshwsh [commandname] --helpcmd/wsh/cmd/wshcmd-root.gopkg/wshrpc/wshclient/pkg/wshrpc/wshrpctypes.godocs/docs/wsh-reference.mdxcmd/wsh/cmd/wshcmd-*.go