Add wsh wavepath command for getting Wave paths (#1545)

This commit is contained in:
Evan Simkowitz 2024-12-17 14:11:40 -08:00 committed by GitHub
parent 799aecd501
commit 0a3dadb628
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 261 additions and 0 deletions

View File

@ -0,0 +1,135 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bytes"
"fmt"
"io"
"os"
"github.com/spf13/cobra"
"github.com/wavetermdev/waveterm/pkg/wshrpc"
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
)
var wavepathCmd = &cobra.Command{
Use: "wavepath {config|data|log}",
Short: "Get paths to various waveterm files and directories",
RunE: wavepathRun,
PreRunE: preRunSetupRpcClient,
}
func init() {
wavepathCmd.Flags().BoolP("open", "o", false, "Open the path in a new block")
wavepathCmd.Flags().BoolP("open-external", "O", false, "Open the path in the default external application")
wavepathCmd.Flags().BoolP("tail", "t", false, "Tail the last 100 lines of the log")
rootCmd.AddCommand(wavepathCmd)
}
func wavepathRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
sendActivity("wavepath", rtnErr == nil)
}()
if len(args) == 0 {
OutputHelpMessage(cmd)
return fmt.Errorf("no arguments. wsh wavepath requires a type argument (config, data, or log)")
}
if len(args) > 1 {
OutputHelpMessage(cmd)
return fmt.Errorf("too many arguments. wsh wavepath requires exactly one argument")
}
pathType := args[0]
if pathType != "config" && pathType != "data" && pathType != "log" {
OutputHelpMessage(cmd)
return fmt.Errorf("invalid path type %q. must be one of: config, data, log", pathType)
}
tail, _ := cmd.Flags().GetBool("tail")
if tail && pathType != "log" {
return fmt.Errorf("--tail can only be used with the log path type")
}
open, _ := cmd.Flags().GetBool("open")
openExternal, _ := cmd.Flags().GetBool("open-external")
path, err := wshclient.PathCommand(RpcClient, wshrpc.PathCommandData{
PathType: pathType,
Open: open,
OpenExternal: openExternal,
}, nil)
if err != nil {
return fmt.Errorf("getting path: %w", err)
}
if tail && pathType == "log" {
err = tailLogFile(path)
if err != nil {
return fmt.Errorf("tailing log file: %w", err)
}
return nil
}
WriteStdout("%s\n", path)
return nil
}
func tailLogFile(path string) error {
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("opening log file: %w", err)
}
defer file.Close()
// Get file size
stat, err := file.Stat()
if err != nil {
return fmt.Errorf("getting file stats: %w", err)
}
// Read last 16KB or whole file if smaller
readSize := int64(16 * 1024)
var offset int64
if stat.Size() > readSize {
offset = stat.Size() - readSize
}
_, err = file.Seek(offset, 0)
if err != nil {
return fmt.Errorf("seeking file: %w", err)
}
buf := make([]byte, readSize)
n, err := file.Read(buf)
if err != nil && err != io.EOF {
return fmt.Errorf("reading file: %w", err)
}
buf = buf[:n]
// Skip partial line at start if we're not at beginning of file
if offset > 0 {
idx := bytes.IndexByte(buf, '\n')
if idx >= 0 {
buf = buf[idx+1:]
}
}
// Split into lines
lines := bytes.Split(buf, []byte{'\n'})
// Take last 100 lines if we have more
startIdx := 0
if len(lines) > 100 {
startIdx = len(lines) - 100
}
// Print lines
for _, line := range lines[startIdx:] {
WriteStdout("%s\n", string(line))
}
return nil
}

View File

@ -693,4 +693,52 @@ wsh setvar -b client MYVAR=value
Variables set with these commands persist across sessions and can be used to store configuration values, secrets, or any other string data that needs to be accessible across blocks or tabs.
## wavepath
The `wavepath` command lets you get the paths to various Wave Terminal directories and files, including configuration, data storage, and logs.
```bash
wsh wavepath {config|data|log}
```
This command returns the full path to the requested Wave Terminal system directory or file. It's useful for accessing Wave's configuration files, data storage, or checking logs.
Flags:
- `-o, --open` - open the path in a new block
- `-O, --open-external` - open the path in the default external application
- `-t, --tail` - show the last ~100 lines of the log file (only valid for log path)
Examples:
```bash
# Get path to config directory
wsh wavepath config
# Get path to data directory
wsh wavepath data
# Get path to log file
wsh wavepath log
# Open log file in a new block
wsh wavepath -o log
# Open config directory in system file explorer
wsh wavepath -O config
# View recent log entries
wsh wavepath -t log
```
The command will show you the full path to:
- `config` - Where Wave Terminal stores its configuration files
- `data` - Where Wave Terminal stores its persistent data
- `log` - The main Wave Terminal log file
:::tip
Use the `-t` flag with the log path to quickly view recent log entries without having to open the full file. This is particularly useful for troubleshooting.
:::
</PlatformProvider>

View File

@ -202,6 +202,11 @@ class RpcApiType {
return client.wshRpcCall("notify", data, opts);
}
// command "path" [call]
PathCommand(client: WshClient, data: PathCommandData, opts?: RpcOpts): Promise<string> {
return client.wshRpcCall("path", data, opts);
}
// command "remotefiledelete" [call]
RemoteFileDeleteCommand(client: WshClient, data: string, opts?: RpcOpts): Promise<void> {
return client.wshRpcCall("remotefiledelete", data, opts);

View File

@ -412,6 +412,11 @@ export class LayoutModel {
for (const action of actions) {
switch (action.actiontype) {
case LayoutTreeActionType.InsertNode: {
if (action.ephemeral) {
this.newEphemeralNode(action.blockid);
break;
}
const insertNodeAction: LayoutTreeInsertNodeAction = {
type: LayoutTreeActionType.InsertNode,
node: newLayoutNode(undefined, undefined, undefined, {

View File

@ -139,6 +139,7 @@ declare global {
blockdef: BlockDef;
rtopts?: RuntimeOpts;
magnified?: boolean;
ephemeral?: boolean;
};
// wshrpc.CommandCreateSubBlockData
@ -400,6 +401,7 @@ declare global {
indexarr?: number[];
focused: boolean;
magnified: boolean;
ephemeral: boolean;
};
// waveobj.LayoutState
@ -562,6 +564,14 @@ declare global {
prompt: OpenAIPromptMessageType[];
};
// wshrpc.PathCommandData
type PathCommandData = {
pathtype: string;
open: boolean;
openexternal: boolean;
tabid: string;
};
// waveobj.Point
type Point = {
x: number;

1
go.mod
View File

@ -20,6 +20,7 @@ require (
github.com/sawka/txwrap v0.2.0
github.com/shirou/gopsutil/v4 v4.24.11
github.com/skeema/knownhosts v1.3.0
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/spf13/cobra v1.8.1
github.com/ubuntu/gowsl v0.0.0-20240906163211-049fd49bd93b
github.com/wavetermdev/htmltoken v0.2.0

2
go.sum
View File

@ -68,6 +68,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=

View File

@ -208,6 +208,7 @@ type LayoutActionData struct {
IndexArr *[]int `json:"indexarr,omitempty"`
Focused bool `json:"focused"`
Magnified bool `json:"magnified"`
Ephemeral bool `json:"ephemeral"`
}
type LeafOrderEntry struct {

View File

@ -247,6 +247,12 @@ func NotifyCommand(w *wshutil.WshRpc, data wshrpc.WaveNotificationOptions, opts
return err
}
// command "path", wshserver.PathCommand
func PathCommand(w *wshutil.WshRpc, data wshrpc.PathCommandData, opts *wshrpc.RpcOpts) (string, error) {
resp, err := sendRpcRequestCallHelper[string](w, "path", data, opts)
return resp, err
}
// command "remotefiledelete", wshserver.RemoteFileDeleteCommand
func RemoteFileDeleteCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "remotefiledelete", data, opts)

View File

@ -149,6 +149,7 @@ type WshRpcInterface interface {
ActivityCommand(ctx context.Context, data ActivityUpdate) error
GetVarCommand(ctx context.Context, data CommandVarData) (*CommandVarResponseData, error)
SetVarCommand(ctx context.Context, data CommandVarData) error
PathCommand(ctx context.Context, data PathCommandData) (string, error)
// connection functions
ConnStatusCommand(ctx context.Context) ([]ConnStatus, error)
@ -290,6 +291,7 @@ type CommandCreateBlockData struct {
BlockDef *waveobj.BlockDef `json:"blockdef"`
RtOpts *waveobj.RuntimeOpts `json:"rtopts,omitempty"`
Magnified bool `json:"magnified,omitempty"`
Ephemeral bool `json:"ephemeral,omitempty"`
}
type CommandCreateSubBlockData struct {
@ -604,6 +606,13 @@ type CommandVarResponseData struct {
Exists bool `json:"exists"`
}
type PathCommandData struct {
PathType string `json:"pathtype"`
Open bool `json:"open"`
OpenExternal bool `json:"openexternal"`
TabId string `json:"tabid" wshcontext:"TabId"`
}
type ActivityDisplayType struct {
Width int `json:"width"`
Height int `json:"height"`

View File

@ -12,10 +12,12 @@ import (
"fmt"
"io/fs"
"log"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/skratchdot/open-golang/open"
"github.com/wavetermdev/waveterm/pkg/blockcontroller"
"github.com/wavetermdev/waveterm/pkg/filestore"
"github.com/wavetermdev/waveterm/pkg/panichandler"
@ -183,6 +185,7 @@ func (ws *WshServer) CreateBlockCommand(ctx context.Context, data wshrpc.Command
ActionType: wcore.LayoutActionDataType_Insert,
BlockId: blockData.OID,
Magnified: data.Magnified,
Ephemeral: data.Ephemeral,
Focused: true,
})
if err != nil {
@ -829,3 +832,39 @@ func (ws *WshServer) SetVarCommand(ctx context.Context, data wshrpc.CommandVarDa
envStr := envutil.MapToEnv(envMap)
return filestore.WFS.WriteFile(ctx, data.ZoneId, data.FileName, []byte(envStr))
}
func (ws *WshServer) PathCommand(ctx context.Context, data wshrpc.PathCommandData) (string, error) {
pathType := data.PathType
openInternal := data.Open
openExternal := data.OpenExternal
var path string
switch pathType {
case "config":
path = wavebase.GetWaveConfigDir()
case "data":
path = wavebase.GetWaveDataDir()
case "log":
path = filepath.Join(wavebase.GetWaveDataDir(), "waveapp.log")
}
if openInternal && openExternal {
return "", fmt.Errorf("open and openExternal cannot both be true")
}
if openInternal {
_, err := ws.CreateBlockCommand(ctx, wshrpc.CommandCreateBlockData{BlockDef: &waveobj.BlockDef{Meta: map[string]any{
waveobj.MetaKey_View: "preview",
waveobj.MetaKey_File: path,
}}, Ephemeral: true, TabId: data.TabId})
if err != nil {
return path, fmt.Errorf("error opening path: %w", err)
}
} else if openExternal {
err := open.Run(path)
if err != nil {
return path, fmt.Errorf("error opening path: %w", err)
}
}
return path, nil
}