add-wshcmd

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Adding a New wsh Command to Wave Terminal

向Wave Terminal添加新wsh命令

This guide explains how to add a new command to the
wsh
CLI tool.
本指南讲解如何向
wsh
CLI工具添加新命令。

wsh Command System Overview

wsh命令系统概述

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.
Wave Terminal的
wsh
命令提供了访问Wave Terminal功能的CLI入口,该系统使用以下组件:
  1. Cobra Framework - CLI命令结构与解析
  2. 命令文件 - 位于
    cmd/wsh/cmd/wshcmd-*.go
    的独立命令实现
  3. RPC Client - 通过
    RpcClient
    与Wave Terminal后端通信
  4. 活动追踪 - 用于命令使用分析的遥测数据
  5. 文档 - 面向用户的文档位于
    docs/docs/wsh-reference.mdx
命令在各自的
init()
函数中注册,通过Cobra框架执行。

Step-by-Step Guide

分步指南

Step 1: Create Command File

步骤1:创建命令文件

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
cmd/wsh/cmd/
目录下创建名为
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
}
文件命名规范:
  • 使用
    wshcmd-[commandname].go
    格式
  • 多词命令使用小写、连字符分隔的名称
  • 示例:
    wshcmd-getvar.go
    wshcmd-setmeta.go
    wshcmd-ai.go

Step 2: Command Structure

步骤2:命令结构

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
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,
}
关键字段:
  • Use
    :命令名称和参数模式
  • Short
    :命令列表中显示的简短描述
  • Long
    :帮助页面显示的详细描述
  • Args
    :参数校验器(可选)
  • RunE
    :主执行函数(返回错误)
  • PreRunE
    :在
    RunE
    之前执行的初始化函数
  • Example
    :使用示例(可选)
  • DisableFlagsInUseLine
    :优化帮助页面的显示效果

When to Use PreRunE

何时使用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
如果你的命令满足以下条件,请添加
PreRunE: preRunSetupRpcClient
  • 需要与Wave Terminal后端通信
  • 需要访问
    RpcClient
  • 需要JWT认证(WAVETERM_JWT环境变量)
  • 通过
    wshclient.*Command()
    函数发起RPC调用
如果你的命令满足以下条件,不要添加PreRunE
  • 仅操作本地状态
  • 不需要后端通信
  • 是纯信息类/本地操作命令

Step 3: Implement Command Logic

步骤3:实现命令逻辑

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
    }
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
}
重要模式:
  1. 活动追踪:始终添加延迟执行的
    sendActivity()
    调用
    go
    defer func() {
        sendActivity("commandname", rtnErr == nil)
    }()
  2. 错误处理:返回错误,不要直接调用
    os.Exit()
    go
    if err != nil {
        return fmt.Errorf("context: %w", err)
    }
  3. 输出:使用标准
    fmt
    包输出内容
    go
    fmt.Printf("Success message\n")
    fmt.Fprintf(os.Stderr, "Error message\n")
  4. 帮助信息:参数无效时显示帮助
    go
    if len(args) == 0 {
        OutputHelpMessage(cmd)
        return fmt.Errorf("requires arguments")
    }
  5. 退出码:通过
    WshExitCode
    设置自定义退出码
    go
    if notFound {
        WshExitCode = 1
        return nil  // Don't return error, just set exit code
    }

Step 4: Define Flags

步骤4:定义参数

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
init()
函数中添加参数:
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")
}
参数类型:
  • StringVar/StringVarP
    - 字符串类型值
  • BoolVar/BoolVarP
    - 布尔类型开关
  • IntVar/IntVarP
    - 整数类型值
  • P
    后缀的版本支持短参数名
参数命名:
  • 变量名使用驼峰命名:
    myCommandFlagName
  • 参数名使用短横线分隔:
    --flag-name
  • 变量名前添加命令名前缀提升可读性

Step 5: Working with Block Arguments

步骤5:处理块参数

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,
})
很多命令会操作块,使用标准块解析模式:
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
}
块解析规则:
  • -b/--block
    参数在
    wshcmd-root.go
    中全局定义
  • resolveBlockArg()
    将块参数解析为完整ORef
  • 支持的值:
    this
    tab
    、完整UUID、8位UUID前缀、块编号
  • 默认值为
    "this"
    (当前块)
备选方案:手动块解析
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

步骤6:发起RPC调用

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)
使用
wshclient
包发起RPC调用:
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选项:
  • Timeout
    :请求超时时间,单位为毫秒(通常为2000-5000)
  • Route
    :指定目标组件的路由ID
  • 可用路由:
    wshutil.ControlRoute
    wshutil.MakeTabRouteId(tabId)

Step 7: Add Documentation

步骤7:添加文档

Add your command to
docs/docs/wsh-reference.mdx
:
markdown
undefined
将你的命令添加到
docs/docs/wsh-reference.mdx
中:
markdown
undefined

mycommand

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
undefined
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
undefined

Basic usage

Basic usage

wsh mycommand arg1
wsh mycommand arg1

With flags

With flags

wsh mycommand --name value arg1
wsh mycommand --name value arg1

With block targeting

With block targeting

wsh mycommand -b 2 arg1
wsh mycommand -b 2 arg1

Complex example

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
wsh mycommand -v --name "example" arg1 arg2

Additional notes, tips, or warnings about the command.

---
文档规范:
  • 按照字母顺序和其他命令放在一起
  • 包含命令签名和参数模式
  • 列出所有参数的短名和长名
  • 提供实用示例(至少3-5个)
  • 说明常见使用场景和模式
  • 如有相关内容添加提示或警告
  • 命令之间使用
    ---
    分隔

Step 8: Test Your Command

步骤8:测试你的命令

Build and test the command:
bash
undefined
构建并测试命令:
bash
undefined

Build wsh

Build wsh

task build:wsh
task build:wsh

Or build everything

Or build everything

task build
task build

Test the command

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
./bin/wsh/wsh mycommand --help ./bin/wsh/wsh mycommand arg1 arg2

**测试检查清单:**
- [ ] 帮助信息显示正常
- [ ] 必选参数校验生效
- [ ] 参数工作符合预期
- [ ] 错误信息清晰明确
- [ ] 成功场景运行正常
- [ ] RPC调用完成成功
- [ ] 输出格式符合要求

Complete Examples

完整示例

Example 1: Simple Command with No RPC

示例1:无RPC的简单命令

Use case: A command that prints Wave Terminal version info
使用场景: 打印Wave Terminal版本信息的命令

Command File (
cmd/wsh/cmd/wshcmd-version.go
)

命令文件 (
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
}
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
undefined
markdown
undefined

version

version

Print the current Wave Terminal version.
sh
wsh version
Examples:
sh
undefined
Print the current Wave Terminal version.
sh
wsh version
Examples:
sh
undefined

Print version

Print version

wsh version
undefined
wsh version
undefined

Example 2: Command with Flags and RPC

示例2:带参数和RPC的命令

Use case: A command to update block title
使用场景: 更新块标题的命令

Command File (
cmd/wsh/cmd/wshcmd-settitle.go
)

命令文件 (
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"
)

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
}
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
}

Documentation

文档

markdown
undefined
markdown
undefined

settitle

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
undefined
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
undefined

Set title for current block

Set title for current block

wsh settitle "My Terminal"
wsh settitle "My Terminal"

Set title and icon

Set title and icon

wsh settitle --icon "terminal" "Development Shell"
wsh settitle --icon "terminal" "Development Shell"

Set title for specific block

Set title for specific block

wsh settitle -b 2 "Build Output"
undefined
wsh settitle -b 2 "Build Output"
undefined

Example 3: Subcommands

示例3:子命令

Use case: Command with multiple subcommands (like
wsh conn
)
使用场景: 带多个子命令的命令(比如
wsh conn

Command File (
cmd/wsh/cmd/wshcmd-mygroup.go
)

命令文件 (
cmd/wsh/cmd/wshcmd-mygroup.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 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
}
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
}

Documentation

文档

markdown
undefined
markdown
undefined

mygroup

mygroup

Manage something with subcommands.
Manage something with subcommands.

list

list

List all items.
sh
wsh mygroup list
List all items.
sh
wsh mygroup list

add

add

Add a new item.
sh
wsh mygroup add [name]
Examples:
sh
undefined
Add a new item.
sh
wsh mygroup add [name]
Examples:
sh
undefined

List items

List items

wsh mygroup list
wsh mygroup list

Add an item

Add an item

wsh mygroup add "new-item"
undefined
wsh mygroup add "new-item"
undefined

Common Patterns

常见模式

Reading from Stdin

从标准输入读取

go
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
}
go
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
}

JSON File Input

JSON文件输入

go
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
}
go
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
}

Conditional Output (TTY Detection)

条件输出(TTY检测)

go
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
}
go
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
}

Environment Variable Access

环境变量访问

go
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
}
go
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
}

Best Practices

最佳实践

Command Design

命令设计

  1. Single Responsibility: Each command should do one thing well
  2. Composable: Design commands to work with pipes and other commands
  3. Consistent: Follow existing wsh command patterns and conventions
  4. Documented: Provide clear help text and examples
  1. 单一职责:每个命令只做好一件事
  2. 可组合:设计的命令要支持管道和其他命令协同工作
  3. 一致性:遵循现有wsh命令的模式和规范
  4. 文档完善:提供清晰的帮助文本和示例

Error Handling

错误处理

  1. Context: Wrap errors with context using
    fmt.Errorf("context: %w", err)
  2. User-Friendly: Make error messages clear and actionable
  3. No Panics: Return errors instead of calling
    os.Exit()
    or
    panic()
  4. Exit Codes: Use
    WshExitCode
    for custom exit codes
  1. 上下文信息:使用
    fmt.Errorf("context: %w", err)
    为错误添加上下文
  2. 用户友好:错误信息要清晰、可操作
  3. 不使用Panic:返回错误而不是调用
    os.Exit()
    panic()
  4. 退出码:使用
    WshExitCode
    设置自定义退出码

Output

输出

  1. Structured: Use consistent formatting for output
  2. Quiet by Default: Only output what's necessary
  3. Verbose Flag: Optionally provide
    -v
    for detailed output
  4. Stderr for Errors: Use
    fmt.Fprintf(os.Stderr, ...)
    for error messages
  1. 结构化:输出使用统一的格式
  2. 默认静默:仅输出必要的内容
  3. 详细参数:可选提供
    -v
    参数输出详细信息
  4. 错误输出到Stderr:使用
    fmt.Fprintf(os.Stderr, ...)
    输出错误信息

Flags

参数

  1. Short Versions: Provide
    -x
    short versions for common flags
  2. Sensible Defaults: Choose defaults that work for most users
  3. Boolean Flags: Use for on/off options
  4. String Flags: Use for values that need user input
  1. 短名支持:常用参数提供
    -x
    格式的短名
  2. 合理默认值:选择适合大多数用户的默认值
  3. 布尔参数:用于开关类型的选项
  4. 字符串参数:用于需要用户输入的值

RPC Calls

RPC调用

  1. Timeouts: Always specify reasonable timeouts
  2. Error Context: Wrap RPC errors with operation context
  3. Retries: Don't retry automatically; let user retry command
  4. Routes: Use appropriate routes for different operations
  1. 超时设置:始终指定合理的超时时间
  2. 错误上下文:为RPC错误添加操作上下文
  3. 不自动重试:不要自动重试,让用户手动重试命令
  4. 路由选择:不同操作使用合适的路由

Common Pitfalls

常见陷阱

1. Forgetting Activity Tracking

1. 忘记添加活动追踪

Problem: Command usage not tracked in telemetry
Solution: Always include deferred
sendActivity()
call:
go
defer func() {
    sendActivity("commandname", rtnErr == nil)
}()
问题:命令使用情况不会被遥测系统记录
解决方案:始终添加延迟执行的
sendActivity()
调用:
go
defer func() {
    sendActivity("commandname", rtnErr == nil)
}()

2. Using os.Exit() Instead of Returning Error

2. 使用os.Exit()而不是返回错误

Problem: Breaks defer statements and cleanup
Solution: Return errors from RunE function:
go
// 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)
}
问题:会中断defer语句和清理逻辑
解决方案:从RunE函数返回错误:
go
// 错误写法
if err != nil {
    fmt.Fprintf(os.Stderr, "error: %v\n", err)
    os.Exit(1)
}

// 正确写法
if err != nil {
    return fmt.Errorf("operation failed: %w", err)
}

3. Not Validating Arguments

3. 没有校验参数

Problem: Command crashes with nil pointer or index out of range
Solution: Validate arguments early and show help:
go
if len(args) == 0 {
    OutputHelpMessage(cmd)
    return fmt.Errorf("requires at least one argument")
}
问题:命令会因为空指针或数组越界崩溃
解决方案:尽早校验参数并显示帮助:
go
if len(args) == 0 {
    OutputHelpMessage(cmd)
    return fmt.Errorf("requires at least one argument")
}

4. Forgetting to Add to init()

4. 忘记在init()中注册命令

Problem: Command not available when running wsh
Solution: Always add command in
init()
function:
go
func init() {
    rootCmd.AddCommand(myCommandCmd)
}
问题:运行wsh时找不到该命令
解决方案:始终在
init()
函数中添加命令注册:
go
func init() {
    rootCmd.AddCommand(myCommandCmd)
}

5. Inconsistent Output

5. 输出方式不一致

Problem: Inconsistent use of output methods
Solution: Use standard
fmt
package functions:
go
// For stdout
fmt.Printf("output\n")

// For stderr
fmt.Fprintf(os.Stderr, "error message\n")
问题:输出方法使用不统一
解决方案:使用标准
fmt
包函数:
go
// 输出到stdout
fmt.Printf("output\n")

// 输出到stderr
fmt.Fprintf(os.Stderr, "error message\n")

Quick Reference Checklist

快速参考检查清单

When adding a new wsh command:
  • Create
    cmd/wsh/cmd/wshcmd-[commandname].go
  • Define command struct with Use, Short, Long descriptions
  • Add
    PreRunE: preRunSetupRpcClient
    if using RPC
  • Implement command function with activity tracking
  • Add command to
    rootCmd
    in
    init()
    function
  • Define flags in
    init()
    function if needed
  • Add documentation to
    docs/docs/wsh-reference.mdx
  • Build and test:
    task build:wsh
  • Test help:
    wsh [commandname] --help
  • Test all flag combinations
  • Test error cases
添加新wsh命令时请确认:
  • 创建了
    cmd/wsh/cmd/wshcmd-[commandname].go
    文件
  • 定义了包含Use、Short、Long描述的命令结构体
  • 如果使用RPC,添加了
    PreRunE: preRunSetupRpcClient
  • 实现了包含活动追踪的命令函数
  • init()
    函数中将命令添加到
    rootCmd
  • 如有需要在
    init()
    函数中定义了参数
  • docs/docs/wsh-reference.mdx
    中添加了文档
  • 构建并测试:
    task build:wsh
  • 测试帮助信息:
    wsh [commandname] --help
  • 测试所有参数组合
  • 测试错误场景

Related Files

相关文件

  • Root Command:
    cmd/wsh/cmd/wshcmd-root.go
    - Main command setup and utilities
  • RPC Client:
    pkg/wshrpc/wshclient/
    - Client functions for RPC calls
  • RPC Types:
    pkg/wshrpc/wshrpctypes.go
    - RPC request/response data structures
  • Documentation:
    docs/docs/wsh-reference.mdx
    - User-facing command reference
  • Examples:
    cmd/wsh/cmd/wshcmd-*.go
    - Existing command implementations
  • 根命令
    cmd/wsh/cmd/wshcmd-root.go
    - 主命令设置和工具函数
  • RPC客户端
    pkg/wshrpc/wshclient/
    - RPC调用的客户端函数
  • RPC类型
    pkg/wshrpc/wshrpctypes.go
    - RPC请求/响应数据结构
  • 文档
    docs/docs/wsh-reference.mdx
    - 面向用户的命令参考
  • 示例
    cmd/wsh/cmd/wshcmd-*.go
    - 现有命令的实现