mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-24 11:14:23 +01:00
Replace environment variable passing of agent data from Node.js to TUI with proper HTTP API call to /agent endpoint. This improves architecture by eliminating env var dependencies and allows dynamic agent data fetching.
154 lines
3.7 KiB
Go
154 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
|
|
tea "github.com/charmbracelet/bubbletea/v2"
|
|
flag "github.com/spf13/pflag"
|
|
"github.com/sst/opencode-sdk-go"
|
|
"github.com/sst/opencode-sdk-go/option"
|
|
"github.com/sst/opencode/internal/api"
|
|
"github.com/sst/opencode/internal/app"
|
|
"github.com/sst/opencode/internal/clipboard"
|
|
"github.com/sst/opencode/internal/tui"
|
|
"github.com/sst/opencode/internal/util"
|
|
)
|
|
|
|
var Version = "dev"
|
|
|
|
func main() {
|
|
version := Version
|
|
if version != "dev" && !strings.HasPrefix(Version, "v") {
|
|
version = "v" + Version
|
|
}
|
|
|
|
var model *string = flag.String("model", "", "model to begin with")
|
|
var prompt *string = flag.String("prompt", "", "prompt to begin with")
|
|
var agent *string = flag.String("agent", "", "agent to begin with")
|
|
var sessionID *string = flag.String("session", "", "session ID")
|
|
flag.Parse()
|
|
|
|
url := os.Getenv("OPENCODE_SERVER")
|
|
|
|
appInfoStr := os.Getenv("OPENCODE_APP_INFO")
|
|
var appInfo opencode.App
|
|
err := json.Unmarshal([]byte(appInfoStr), &appInfo)
|
|
if err != nil {
|
|
slog.Error("Failed to unmarshal app info", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
stat, err := os.Stdin.Stat()
|
|
if err != nil {
|
|
slog.Error("Failed to stat stdin", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Check if there's data piped to stdin
|
|
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
|
stdin, err := io.ReadAll(os.Stdin)
|
|
if err != nil {
|
|
slog.Error("Failed to read stdin", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
stdinContent := strings.TrimSpace(string(stdin))
|
|
if stdinContent != "" {
|
|
if prompt == nil || *prompt == "" {
|
|
prompt = &stdinContent
|
|
} else {
|
|
combined := *prompt + "\n" + stdinContent
|
|
prompt = &combined
|
|
}
|
|
}
|
|
}
|
|
|
|
httpClient := opencode.NewClient(
|
|
option.WithBaseURL(url),
|
|
)
|
|
|
|
// Fetch agents from the /agent endpoint
|
|
agentsPtr, err := httpClient.App.Agents(context.Background())
|
|
if err != nil {
|
|
slog.Error("Failed to fetch agents", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
if agentsPtr == nil {
|
|
slog.Error("No agents returned from server")
|
|
os.Exit(1)
|
|
}
|
|
agents := *agentsPtr
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
apiHandler := util.NewAPILogHandler(ctx, httpClient, "tui", slog.LevelDebug)
|
|
logger := slog.New(apiHandler)
|
|
slog.SetDefault(logger)
|
|
|
|
slog.Debug("TUI launched", "app", appInfoStr, "agents_count", len(agents), "url", url)
|
|
|
|
go func() {
|
|
err = clipboard.Init()
|
|
if err != nil {
|
|
slog.Error("Failed to initialize clipboard", "error", err)
|
|
}
|
|
}()
|
|
|
|
// Create main context for the application
|
|
app_, err := app.New(ctx, version, appInfo, agents, httpClient, model, prompt, agent, sessionID)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
tuiModel := tui.NewModel(app_).(*tui.Model)
|
|
program := tea.NewProgram(
|
|
tuiModel,
|
|
tea.WithAltScreen(),
|
|
tea.WithMouseCellMotion(),
|
|
)
|
|
|
|
// Set up signal handling for graceful shutdown
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
|
|
|
|
go func() {
|
|
stream := httpClient.Event.ListStreaming(ctx)
|
|
for stream.Next() {
|
|
evt := stream.Current().AsUnion()
|
|
if _, ok := evt.(opencode.EventListResponseEventStorageWrite); ok {
|
|
continue
|
|
}
|
|
program.Send(evt)
|
|
}
|
|
if err := stream.Err(); err != nil {
|
|
slog.Error("Error streaming events", "error", err)
|
|
program.Send(err)
|
|
}
|
|
}()
|
|
|
|
go api.Start(ctx, program, httpClient)
|
|
|
|
// Handle signals in a separate goroutine
|
|
go func() {
|
|
sig := <-sigChan
|
|
slog.Info("Received signal, shutting down gracefully", "signal", sig)
|
|
tuiModel.Cleanup()
|
|
program.Quit()
|
|
}()
|
|
|
|
// Run the TUI
|
|
result, err := program.Run()
|
|
if err != nil {
|
|
slog.Error("TUI error", "error", err)
|
|
}
|
|
|
|
tuiModel.Cleanup()
|
|
slog.Info("TUI exited", "result", result)
|
|
}
|