+
+
void; // close-builder-window
incrementTermCommands: () => void; // increment-term-commands
nativePaste: () => void; // native-paste
+ openBuilder: (appId?: string) => void; // open-builder
+ setBuilderWindowAppId: (appId: string) => void; // set-builder-window-appid
};
type ElectronContextMenuItem = {
@@ -292,6 +294,9 @@ declare global {
// Icon representing the view, can be a string or an IconButton declaration.
viewIcon?: jotai.Atom;
+ // Optional color for the view icon.
+ viewIconColor?: jotai.Atom;
+
// Display name for the view, used in UI headers.
viewName?: jotai.Atom;
diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts
index 9353f79edb..d72680e37c 100644
--- a/frontend/types/gotypes.d.ts
+++ b/frontend/types/gotypes.d.ts
@@ -64,13 +64,20 @@ declare global {
// wshrpc.AppManifest
type AppManifest = {
- apptitle: string;
- appshortdesc: string;
+ appmeta: AppMeta;
configschema: {[key: string]: any};
dataschema: {[key: string]: any};
secrets: {[key: string]: SecretMeta};
};
+ // wshrpc.AppMeta
+ type AppMeta = {
+ title: string;
+ shortdesc: string;
+ icon: string;
+ iconcolor: string;
+ };
+
// waveobj.Block
type Block = WaveObj & {
parentoref?: string;
@@ -331,6 +338,16 @@ declare global {
truncated?: boolean;
};
+ // wshrpc.CommandMakeDraftFromLocalData
+ type CommandMakeDraftFromLocalData = {
+ localappid: string;
+ };
+
+ // wshrpc.CommandMakeDraftFromLocalRtnData
+ type CommandMakeDraftFromLocalRtnData = {
+ draftappid: string;
+ };
+
// wshrpc.CommandMessageData
type CommandMessageData = {
oref: ORef;
@@ -504,6 +521,17 @@ declare global {
data64: string;
};
+ // wshrpc.CommandWriteAppGoFileData
+ type CommandWriteAppGoFileData = {
+ appid: string;
+ data64: string;
+ };
+
+ // wshrpc.CommandWriteAppGoFileRtnData
+ type CommandWriteAppGoFileRtnData = {
+ data64: string;
+ };
+
// wshrpc.CommandWriteAppSecretBindingsData
type CommandWriteAppSecretBindingsData = {
appid: string;
@@ -887,6 +915,8 @@ declare global {
type ObjRTInfo = {
"tsunami:title"?: string;
"tsunami:shortdesc"?: string;
+ "tsunami:icon"?: string;
+ "tsunami:iconcolor"?: string;
"tsunami:schemas"?: any;
"shell:hascurcwd"?: boolean;
"shell:state"?: string;
diff --git a/frontend/wave.ts b/frontend/wave.ts
index 92e77614c9..6ebe907951 100644
--- a/frontend/wave.ts
+++ b/frontend/wave.ts
@@ -265,6 +265,11 @@ async function initBuilder(initOpts: BuilderInitOpts) {
const rtInfo = await RpcApi.GetRTInfoCommand(TabRpcClient, { oref });
if (rtInfo && rtInfo["builder:appid"]) {
appIdToUse = rtInfo["builder:appid"];
+ } else if (appIdToUse) {
+ await RpcApi.SetRTInfoCommand(TabRpcClient, {
+ oref,
+ data: { "builder:appid": appIdToUse },
+ });
}
} catch (e) {
console.log("Could not load saved builder appId from rtinfo:", e);
diff --git a/pkg/aiusechat/tools_builder.go b/pkg/aiusechat/tools_builder.go
index 1df1cd0221..14e71b8f45 100644
--- a/pkg/aiusechat/tools_builder.go
+++ b/pkg/aiusechat/tools_builder.go
@@ -15,6 +15,7 @@ import (
"github.com/wavetermdev/waveterm/pkg/util/fileutil"
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
"github.com/wavetermdev/waveterm/pkg/waveappstore"
+ "github.com/wavetermdev/waveterm/pkg/waveapputil"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wps"
"github.com/wavetermdev/waveterm/pkg/wstore"
@@ -119,7 +120,8 @@ func GetBuilderWriteAppFileToolDefinition(appId string, builderId string) uctype
return nil, err
}
- err = waveappstore.WriteAppFile(appId, BuilderAppFileName, []byte(params.Contents))
+ formattedContents := waveapputil.FormatGoCode([]byte(params.Contents))
+ err = waveappstore.WriteAppFile(appId, BuilderAppFileName, formattedContents)
if err != nil {
return nil, err
}
@@ -253,6 +255,9 @@ func GetBuilderEditAppFileToolDefinition(appId string, builderId string) uctypes
return nil, err
}
+ // ignore format errors; gofmt can fail due to compilation errors which will be caught in the build step
+ waveappstore.FormatGoFile(appId, BuilderAppFileName)
+
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_WaveAppAppGoUpdated,
Scopes: []string{appId},
diff --git a/pkg/blockcontroller/tsunamicontroller.go b/pkg/blockcontroller/tsunamicontroller.go
index 29d0b500a4..41daaf7601 100644
--- a/pkg/blockcontroller/tsunamicontroller.go
+++ b/pkg/blockcontroller/tsunamicontroller.go
@@ -21,6 +21,7 @@ import (
"github.com/wavetermdev/waveterm/pkg/tsunamiutil"
"github.com/wavetermdev/waveterm/pkg/utilds"
"github.com/wavetermdev/waveterm/pkg/waveappstore"
+ "github.com/wavetermdev/waveterm/pkg/waveapputil"
"github.com/wavetermdev/waveterm/pkg/wavebase"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wconfig"
@@ -29,8 +30,6 @@ import (
"github.com/wavetermdev/waveterm/tsunami/build"
)
-const DefaultTsunamiSdkVersion = "v0.12.2"
-
type TsunamiAppProc struct {
Cmd *exec.Cmd
LineBuffer *utilds.MultiReaderLineBuffer
@@ -126,21 +125,19 @@ func (c *TsunamiController) Start(ctx context.Context, blockMeta waveobj.MetaMap
c.runLock.Lock()
defer c.runLock.Unlock()
+ scaffoldPath := waveapputil.GetTsunamiScaffoldPath()
settings := wconfig.GetWatcher().GetFullConfig().Settings
- scaffoldPath := settings.TsunamiScaffoldPath
- if scaffoldPath == "" {
- scaffoldPath = filepath.Join(wavebase.GetWaveAppPath(), "tsunamiscaffold")
- }
sdkReplacePath := settings.TsunamiSdkReplacePath
sdkVersion := settings.TsunamiSdkVersion
if sdkVersion == "" {
- sdkVersion = DefaultTsunamiSdkVersion
+ sdkVersion = waveapputil.DefaultTsunamiSdkVersion
}
goPath := settings.TsunamiGoPath
appPath := blockMeta.GetString(waveobj.MetaKey_TsunamiAppPath, "")
+ appId := blockMeta.GetString(waveobj.MetaKey_TsunamiAppId, "")
+
if appPath == "" {
- appId := blockMeta.GetString(waveobj.MetaKey_TsunamiAppId, "")
if appId == "" {
return fmt.Errorf("tsunami:apppath or tsunami:appid is required")
}
@@ -160,6 +157,34 @@ func (c *TsunamiController) Start(ctx context.Context, blockMeta waveobj.MetaMap
}
}
+ // Read and set app metadata from manifest if appId is available
+ if appId != "" {
+ if manifest, err := waveappstore.ReadAppManifest(appId); err == nil {
+ blockRef := waveobj.MakeORef(waveobj.OType_Block, c.blockId)
+ rtInfo := make(map[string]any)
+ if manifest.AppMeta.Title != "" {
+ rtInfo["tsunami:title"] = manifest.AppMeta.Title
+ }
+ if manifest.AppMeta.ShortDesc != "" {
+ rtInfo["tsunami:shortdesc"] = manifest.AppMeta.ShortDesc
+ }
+ if manifest.AppMeta.Icon != "" {
+ rtInfo["tsunami:icon"] = manifest.AppMeta.Icon
+ }
+ if manifest.AppMeta.IconColor != "" {
+ rtInfo["tsunami:iconcolor"] = manifest.AppMeta.IconColor
+ }
+ if len(rtInfo) > 0 {
+ wstore.SetRTInfo(blockRef, rtInfo)
+ wps.Broker.Publish(wps.WaveEvent{
+ Event: wps.Event_TsunamiUpdateMeta,
+ Scopes: []string{waveobj.MakeORef(waveobj.OType_Block, c.blockId).String()},
+ Data: manifest.AppMeta,
+ })
+ }
+ }
+ }
+
appName := build.GetAppName(appPath)
osArch := runtime.GOOS + "-" + runtime.GOARCH
diff --git a/pkg/buildercontroller/buildercontroller.go b/pkg/buildercontroller/buildercontroller.go
index 2394a31317..11ccfe1642 100644
--- a/pkg/buildercontroller/buildercontroller.go
+++ b/pkg/buildercontroller/buildercontroller.go
@@ -19,6 +19,7 @@ import (
"github.com/wavetermdev/waveterm/pkg/panichandler"
"github.com/wavetermdev/waveterm/pkg/utilds"
"github.com/wavetermdev/waveterm/pkg/waveappstore"
+ "github.com/wavetermdev/waveterm/pkg/waveapputil"
"github.com/wavetermdev/waveterm/pkg/wavebase"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wconfig"
@@ -208,15 +209,12 @@ func (bc *BuilderController) buildAndRun(ctx context.Context, appId string, buil
return
}
+ scaffoldPath := waveapputil.GetTsunamiScaffoldPath()
settings := wconfig.GetWatcher().GetFullConfig().Settings
- scaffoldPath := settings.TsunamiScaffoldPath
- if scaffoldPath == "" {
- scaffoldPath = filepath.Join(wavebase.GetWaveAppPath(), "tsunamiscaffold")
- }
sdkReplacePath := settings.TsunamiSdkReplacePath
sdkVersion := settings.TsunamiSdkVersion
if sdkVersion == "" {
- sdkVersion = "v0.12.2"
+ sdkVersion = waveapputil.DefaultTsunamiSdkVersion
}
goPath := settings.TsunamiGoPath
@@ -512,8 +510,10 @@ func (bc *BuilderController) GetStatus() wshrpc.BuilderStatusData {
manifest, err := waveappstore.ReadAppManifest(bc.appId)
if err == nil && manifest != nil {
wshrpcManifest := &wshrpc.AppManifest{
- AppTitle: manifest.AppTitle,
- AppShortDesc: manifest.AppShortDesc,
+ AppMeta: wshrpc.AppMeta{
+ Title: manifest.AppMeta.Title,
+ ShortDesc: manifest.AppMeta.ShortDesc,
+ },
ConfigSchema: manifest.ConfigSchema,
DataSchema: manifest.DataSchema,
Secrets: make(map[string]wshrpc.SecretMeta),
diff --git a/pkg/waveappstore/waveappstore.go b/pkg/waveappstore/waveappstore.go
index c9af0a6777..87d61302a9 100644
--- a/pkg/waveappstore/waveappstore.go
+++ b/pkg/waveappstore/waveappstore.go
@@ -7,12 +7,14 @@ import (
"encoding/json"
"fmt"
"os"
+ "os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/wavetermdev/waveterm/pkg/secretstore"
"github.com/wavetermdev/waveterm/pkg/util/fileutil"
+ "github.com/wavetermdev/waveterm/pkg/waveapputil"
"github.com/wavetermdev/waveterm/pkg/wavebase"
"github.com/wavetermdev/waveterm/pkg/wshrpc"
"github.com/wavetermdev/waveterm/tsunami/engine"
@@ -405,6 +407,38 @@ func RenameAppFile(appId string, fromFileName string, toFileName string) error {
return nil
}
+func FormatGoFile(appId string, fileName string) error {
+ if err := ValidateAppId(appId); err != nil {
+ return fmt.Errorf("invalid appId: %w", err)
+ }
+
+ appDir, err := GetAppDir(appId)
+ if err != nil {
+ return err
+ }
+
+ filePath, err := validateAndResolveFilePath(appDir, fileName)
+ if err != nil {
+ return err
+ }
+
+ if filepath.Ext(filePath) != ".go" {
+ return fmt.Errorf("file is not a Go file: %s", fileName)
+ }
+
+ gofmtPath, err := waveapputil.ResolveGoFmtPath()
+ if err != nil {
+ return fmt.Errorf("failed to resolve gofmt path: %w", err)
+ }
+
+ cmd := exec.Command(gofmtPath, "-w", filePath)
+ if output, err := cmd.CombinedOutput(); err != nil {
+ return fmt.Errorf("gofmt failed: %w\nOutput: %s", err, string(output))
+ }
+
+ return nil
+}
+
func ListAllAppFiles(appId string) (*fileutil.ReadDirResult, error) {
if err := ValidateAppId(appId); err != nil {
return nil, fmt.Errorf("invalid appId: %w", err)
diff --git a/pkg/waveapputil/waveapputil.go b/pkg/waveapputil/waveapputil.go
new file mode 100644
index 0000000000..c1bee88188
--- /dev/null
+++ b/pkg/waveapputil/waveapputil.go
@@ -0,0 +1,79 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package waveapputil
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+
+ "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/wavetermdev/waveterm/pkg/wconfig"
+ "github.com/wavetermdev/waveterm/tsunami/build"
+)
+
+const DefaultTsunamiSdkVersion = "v0.12.2"
+
+func GetTsunamiScaffoldPath() string {
+ settings := wconfig.GetWatcher().GetFullConfig().Settings
+ scaffoldPath := settings.TsunamiScaffoldPath
+ if scaffoldPath == "" {
+ scaffoldPath = filepath.Join(wavebase.GetWaveAppPath(), "tsunamiscaffold")
+ }
+ return scaffoldPath
+}
+
+func ResolveGoFmtPath() (string, error) {
+ settings := wconfig.GetWatcher().GetFullConfig().Settings
+ goPath := settings.TsunamiGoPath
+
+ if goPath == "" {
+ var err error
+ goPath, err = build.FindGoExecutable()
+ if err != nil {
+ return "", err
+ }
+ }
+
+ goDir := filepath.Dir(goPath)
+ gofmtName := "gofmt"
+ if runtime.GOOS == "windows" {
+ gofmtName = "gofmt.exe"
+ }
+ gofmtPath := filepath.Join(goDir, gofmtName)
+
+ info, err := os.Stat(gofmtPath)
+ if err != nil {
+ return "", fmt.Errorf("gofmt not found at %s: %w", gofmtPath, err)
+ }
+
+ if info.IsDir() {
+ return "", fmt.Errorf("gofmt path is a directory: %s", gofmtPath)
+ }
+
+ if info.Mode()&0111 == 0 {
+ return "", fmt.Errorf("gofmt is not executable: %s", gofmtPath)
+ }
+
+ return gofmtPath, nil
+}
+
+func FormatGoCode(contents []byte) []byte {
+ gofmtPath, err := ResolveGoFmtPath()
+ if err != nil {
+ return contents
+ }
+
+ cmd := exec.Command(gofmtPath)
+ cmd.Stdin = strings.NewReader(string(contents))
+ formattedOutput, err := cmd.Output()
+ if err != nil {
+ return contents
+ }
+
+ return formattedOutput
+}
\ No newline at end of file
diff --git a/pkg/waveobj/objrtinfo.go b/pkg/waveobj/objrtinfo.go
index 30afe48b07..fcbef2331b 100644
--- a/pkg/waveobj/objrtinfo.go
+++ b/pkg/waveobj/objrtinfo.go
@@ -6,6 +6,8 @@ package waveobj
type ObjRTInfo struct {
TsunamiTitle string `json:"tsunami:title,omitempty"`
TsunamiShortDesc string `json:"tsunami:shortdesc,omitempty"`
+ TsunamiIcon string `json:"tsunami:icon,omitempty"`
+ TsunamiIconColor string `json:"tsunami:iconcolor,omitempty"`
TsunamiSchemas any `json:"tsunami:schemas,omitempty"`
ShellHasCurCwd bool `json:"shell:hascurcwd,omitempty"`
@@ -22,7 +24,7 @@ type ObjRTInfo struct {
BuilderAppId string `json:"builder:appid,omitempty"`
BuilderEnv map[string]string `json:"builder:env,omitempty"`
- WaveAIChatId string `json:"waveai:chatid,omitempty"`
- WaveAIThinkingMode string `json:"waveai:thinkingmode,omitempty"`
- WaveAIMaxOutputTokens int `json:"waveai:maxoutputtokens,omitempty"`
+ WaveAIChatId string `json:"waveai:chatid,omitempty"`
+ WaveAIThinkingMode string `json:"waveai:thinkingmode,omitempty"`
+ WaveAIMaxOutputTokens int `json:"waveai:maxoutputtokens,omitempty"`
}
diff --git a/pkg/wps/wpstypes.go b/pkg/wps/wpstypes.go
index cae43908ff..076d964708 100644
--- a/pkg/wps/wpstypes.go
+++ b/pkg/wps/wpstypes.go
@@ -20,6 +20,7 @@ const (
Event_WorkspaceUpdate = "workspace:update"
Event_WaveAIRateLimit = "waveai:ratelimit"
Event_WaveAppAppGoUpdated = "waveapp:appgoupdated"
+ Event_TsunamiUpdateMeta = "tsunami:updatemeta"
)
type WaveEvent struct {
diff --git a/pkg/wshrpc/wshclient/wshclient.go b/pkg/wshrpc/wshclient/wshclient.go
index 79daaa5ba8..31d9c4e16c 100644
--- a/pkg/wshrpc/wshclient/wshclient.go
+++ b/pkg/wshrpc/wshclient/wshclient.go
@@ -446,6 +446,12 @@ func ListAllEditableAppsCommand(w *wshutil.WshRpc, opts *wshrpc.RpcOpts) ([]wshr
return resp, err
}
+// command "makedraftfromlocal", wshserver.MakeDraftFromLocalCommand
+func MakeDraftFromLocalCommand(w *wshutil.WshRpc, data wshrpc.CommandMakeDraftFromLocalData, opts *wshrpc.RpcOpts) (*wshrpc.CommandMakeDraftFromLocalRtnData, error) {
+ resp, err := sendRpcRequestCallHelper[*wshrpc.CommandMakeDraftFromLocalRtnData](w, "makedraftfromlocal", data, opts)
+ return resp, err
+}
+
// command "message", wshserver.MessageCommand
func MessageCommand(w *wshutil.WshRpc, data wshrpc.CommandMessageData, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "message", data, opts)
@@ -749,6 +755,12 @@ func WriteAppFileCommand(w *wshutil.WshRpc, data wshrpc.CommandWriteAppFileData,
return err
}
+// command "writeappgofile", wshserver.WriteAppGoFileCommand
+func WriteAppGoFileCommand(w *wshutil.WshRpc, data wshrpc.CommandWriteAppGoFileData, opts *wshrpc.RpcOpts) (*wshrpc.CommandWriteAppGoFileRtnData, error) {
+ resp, err := sendRpcRequestCallHelper[*wshrpc.CommandWriteAppGoFileRtnData](w, "writeappgofile", data, opts)
+ return resp, err
+}
+
// command "writeappsecretbindings", wshserver.WriteAppSecretBindingsCommand
func WriteAppSecretBindingsCommand(w *wshutil.WshRpc, data wshrpc.CommandWriteAppSecretBindingsData, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "writeappsecretbindings", data, opts)
diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go
index bb2a3708f6..033346360d 100644
--- a/pkg/wshrpc/wshrpctypes.go
+++ b/pkg/wshrpc/wshrpctypes.go
@@ -161,6 +161,7 @@ const (
Command_ListAllAppFiles = "listallappfiles"
Command_ReadAppFile = "readappfile"
Command_WriteAppFile = "writeappfile"
+ Command_WriteAppGoFile = "writeappgofile"
Command_DeleteAppFile = "deleteappfile"
Command_RenameAppFile = "renameappfile"
Command_WriteAppSecretBindings = "writeappsecretbindings"
@@ -171,6 +172,7 @@ const (
Command_GetBuilderOutput = "getbuilderoutput"
Command_CheckGoVersion = "checkgoversion"
Command_PublishApp = "publishapp"
+ Command_MakeDraftFromLocal = "makedraftfromlocal"
// electron
Command_ElectronEncrypt = "electronencrypt"
@@ -333,6 +335,7 @@ type WshRpcInterface interface {
ListAllAppFilesCommand(ctx context.Context, data CommandListAllAppFilesData) (*CommandListAllAppFilesRtnData, error)
ReadAppFileCommand(ctx context.Context, data CommandReadAppFileData) (*CommandReadAppFileRtnData, error)
WriteAppFileCommand(ctx context.Context, data CommandWriteAppFileData) error
+ WriteAppGoFileCommand(ctx context.Context, data CommandWriteAppGoFileData) (*CommandWriteAppGoFileRtnData, error)
DeleteAppFileCommand(ctx context.Context, data CommandDeleteAppFileData) error
RenameAppFileCommand(ctx context.Context, data CommandRenameAppFileData) error
WriteAppSecretBindingsCommand(ctx context.Context, data CommandWriteAppSecretBindingsData) error
@@ -343,6 +346,7 @@ type WshRpcInterface interface {
GetBuilderOutputCommand(ctx context.Context, builderId string) ([]string, error)
CheckGoVersionCommand(ctx context.Context) (*CommandCheckGoVersionRtnData, error)
PublishAppCommand(ctx context.Context, data CommandPublishAppData) (*CommandPublishAppRtnData, error)
+ MakeDraftFromLocalCommand(ctx context.Context, data CommandMakeDraftFromLocalData) (*CommandMakeDraftFromLocalRtnData, error)
// proc
VDomRenderCommand(ctx context.Context, data vdom.VDomFrontendUpdate) chan RespOrErrorUnion[*vdom.VDomBackendUpdate]
@@ -1008,6 +1012,15 @@ type CommandWriteAppFileData struct {
Data64 string `json:"data64"`
}
+type CommandWriteAppGoFileData struct {
+ AppId string `json:"appid"`
+ Data64 string `json:"data64"`
+}
+
+type CommandWriteAppGoFileRtnData struct {
+ Data64 string `json:"data64"`
+}
+
type CommandDeleteAppFileData struct {
AppId string `json:"appid"`
FileName string `json:"filename"`
@@ -1038,14 +1051,20 @@ type RestartBuilderAndWaitResult struct {
BuildOutput string `json:"buildoutput"`
}
+type AppMeta struct {
+ Title string `json:"title"`
+ ShortDesc string `json:"shortdesc"`
+ Icon string `json:"icon"`
+ IconColor string `json:"iconcolor"`
+}
+
type SecretMeta struct {
Desc string `json:"desc"`
Optional bool `json:"optional"`
}
type AppManifest struct {
- AppTitle string `json:"apptitle"`
- AppShortDesc string `json:"appshortdesc"`
+ AppMeta AppMeta `json:"appmeta"`
ConfigSchema map[string]any `json:"configschema"`
DataSchema map[string]any `json:"dataschema"`
Secrets map[string]SecretMeta `json:"secrets"`
@@ -1077,6 +1096,14 @@ type CommandPublishAppRtnData struct {
PublishedAppId string `json:"publishedappid"`
}
+type CommandMakeDraftFromLocalData struct {
+ LocalAppId string `json:"localappid"`
+}
+
+type CommandMakeDraftFromLocalRtnData struct {
+ DraftAppId string `json:"draftappid"`
+}
+
type CommandElectronEncryptData struct {
PlainText string `json:"plaintext"`
}
diff --git a/pkg/wshrpc/wshserver/wshserver.go b/pkg/wshrpc/wshserver/wshserver.go
index 41019fe2e1..ca4bc49329 100644
--- a/pkg/wshrpc/wshserver/wshserver.go
+++ b/pkg/wshrpc/wshserver/wshserver.go
@@ -46,6 +46,7 @@ import (
"github.com/wavetermdev/waveterm/pkg/util/wavefileutil"
"github.com/wavetermdev/waveterm/pkg/waveai"
"github.com/wavetermdev/waveterm/pkg/waveappstore"
+ "github.com/wavetermdev/waveterm/pkg/waveapputil"
"github.com/wavetermdev/waveterm/pkg/wavebase"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wcloud"
@@ -1013,6 +1014,26 @@ func (ws *WshServer) WriteAppFileCommand(ctx context.Context, data wshrpc.Comman
return waveappstore.WriteAppFile(data.AppId, data.FileName, contents)
}
+func (ws *WshServer) WriteAppGoFileCommand(ctx context.Context, data wshrpc.CommandWriteAppGoFileData) (*wshrpc.CommandWriteAppGoFileRtnData, error) {
+ if data.AppId == "" {
+ return nil, fmt.Errorf("must provide an appId to WriteAppGoFileCommand")
+ }
+ contents, err := base64.StdEncoding.DecodeString(data.Data64)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode data64: %w", err)
+ }
+
+ formattedOutput := waveapputil.FormatGoCode(contents)
+
+ err = waveappstore.WriteAppFile(data.AppId, "app.go", formattedOutput)
+ if err != nil {
+ return nil, err
+ }
+
+ encoded := base64.StdEncoding.EncodeToString(formattedOutput)
+ return &wshrpc.CommandWriteAppGoFileRtnData{Data64: encoded}, nil
+}
+
func (ws *WshServer) DeleteAppFileCommand(ctx context.Context, data wshrpc.CommandDeleteAppFileData) error {
if data.AppId == "" {
return fmt.Errorf("must provide an appId to DeleteAppFileCommand")
@@ -1128,6 +1149,16 @@ func (ws *WshServer) PublishAppCommand(ctx context.Context, data wshrpc.CommandP
}, nil
}
+func (ws *WshServer) MakeDraftFromLocalCommand(ctx context.Context, data wshrpc.CommandMakeDraftFromLocalData) (*wshrpc.CommandMakeDraftFromLocalRtnData, error) {
+ draftAppId, err := waveappstore.MakeDraftFromLocal(data.LocalAppId)
+ if err != nil {
+ return nil, fmt.Errorf("error making draft from local: %w", err)
+ }
+ return &wshrpc.CommandMakeDraftFromLocalRtnData{
+ DraftAppId: draftAppId,
+ }, nil
+}
+
func (ws *WshServer) RecordTEventCommand(ctx context.Context, data telemetrydata.TEvent) error {
err := telemetry.RecordTEvent(ctx, &data)
if err != nil {
diff --git a/tsunami/engine/clientimpl.go b/tsunami/engine/clientimpl.go
index ffcf5fd736..73aeacfcfb 100644
--- a/tsunami/engine/clientimpl.go
+++ b/tsunami/engine/clientimpl.go
@@ -43,6 +43,8 @@ var defaultClient = makeClient()
type AppMeta struct {
Title string `json:"title"`
ShortDesc string `json:"shortdesc"`
+ Icon string `json:"icon"` // for waveapps, the icon to use (fontawesome names)
+ IconColor string `json:"iconcolor"` // for waveapps, the icon color to use (HTML color -- name, hex, rgb)
}
type SecretMeta struct {
@@ -51,11 +53,10 @@ type SecretMeta struct {
}
type AppManifest struct {
- AppTitle string `json:"apptitle"`
- AppShortDesc string `json:"appshortdesc"`
- ConfigSchema map[string]any `json:"configschema"`
- DataSchema map[string]any `json:"dataschema"`
- Secrets map[string]SecretMeta `json:"secrets"`
+ AppMeta AppMeta `json:"appmeta"`
+ ConfigSchema map[string]any `json:"configschema"`
+ DataSchema map[string]any `json:"dataschema"`
+ Secrets map[string]SecretMeta `json:"secrets"`
}
type ClientImpl struct {
@@ -498,10 +499,9 @@ func (c *ClientImpl) GetAppManifest() AppManifest {
configSchema := GenerateConfigSchema(c.Root)
dataSchema := GenerateDataSchema(c.Root)
secrets := c.GetSecrets()
-
+
return AppManifest{
- AppTitle: appMeta.Title,
- AppShortDesc: appMeta.ShortDesc,
+ AppMeta: appMeta,
ConfigSchema: configSchema,
DataSchema: dataSchema,
Secrets: secrets,