summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamdotdevin <[email protected]>2025-07-31 11:24:23 -0500
committeradamdotdevin <[email protected]>2025-07-31 11:24:23 -0500
commit872b1e068f1307326d733663d7b79e0fb75d7232 (patch)
treee554eb0984a92313b3dd9f083b62f7995b4531bb
parente4e0b8fd344273f48ef66e68d9626de80111cc0b (diff)
downloadopencode-872b1e068f1307326d733663d7b79e0fb75d7232.tar.gz
opencode-872b1e068f1307326d733663d7b79e0fb75d7232.zip
feat: more scriptable tui (api)
-rw-r--r--packages/opencode/src/server/server.ts114
-rw-r--r--packages/sdk/stainless/stainless.yml6
-rw-r--r--packages/tui/cmd/opencode/main.go2
-rw-r--r--packages/tui/internal/tui/tui.go37
4 files changed, 158 insertions, 1 deletions
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index b18030f15..76da0fbf5 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -905,6 +905,120 @@ export namespace Server {
}),
async (c) => c.json(await callTui(c)),
)
+ .post(
+ "/tui/open-sessions",
+ describeRoute({
+ description: "Open the session dialog",
+ operationId: "tui.openSessions",
+ responses: {
+ 200: {
+ description: "Session dialog opened successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/open-themes",
+ describeRoute({
+ description: "Open the theme dialog",
+ operationId: "tui.openThemes",
+ responses: {
+ 200: {
+ description: "Theme dialog opened successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/open-models",
+ describeRoute({
+ description: "Open the model dialog",
+ operationId: "tui.openModels",
+ responses: {
+ 200: {
+ description: "Model dialog opened successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/submit-prompt",
+ describeRoute({
+ description: "Submit the prompt",
+ operationId: "tui.submitPrompt",
+ responses: {
+ 200: {
+ description: "Prompt submitted successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/clear-prompt",
+ describeRoute({
+ description: "Clear the prompt",
+ operationId: "tui.clearPrompt",
+ responses: {
+ 200: {
+ description: "Prompt cleared successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/execute-command",
+ describeRoute({
+ description: "Execute a TUI command (e.g. switch_mode)",
+ operationId: "tui.executeCommand",
+ responses: {
+ 200: {
+ description: "Command executed successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
+ },
+ },
+ },
+ },
+ }),
+ zValidator(
+ "json",
+ z.object({
+ command: z.string(),
+ }),
+ ),
+ async (c) => c.json(await callTui(c)),
+ )
.route("/tui/control", TuiRoute)
return result
diff --git a/packages/sdk/stainless/stainless.yml b/packages/sdk/stainless/stainless.yml
index ab40f8777..b9d69cdef 100644
--- a/packages/sdk/stainless/stainless.yml
+++ b/packages/sdk/stainless/stainless.yml
@@ -134,7 +134,13 @@ resources:
tui:
methods:
appendPrompt: post /tui/append-prompt
+ submitPrompt: post /tui/submit-prompt
+ clearPrompt: post /tui/clear-prompt
openHelp: post /tui/open-help
+ openSessions: post /tui/open-sessions
+ openThemes: post /tui/open-themes
+ openModels: post /tui/open-models
+ executeCommand: post /tui/execute-command
settings:
disable_mock_tests: true
diff --git a/packages/tui/cmd/opencode/main.go b/packages/tui/cmd/opencode/main.go
index 66888fe40..5532289fd 100644
--- a/packages/tui/cmd/opencode/main.go
+++ b/packages/tui/cmd/opencode/main.go
@@ -86,7 +86,7 @@ func main() {
logger := slog.New(apiHandler)
slog.SetDefault(logger)
- slog.Debug("TUI launched", "app", appInfoStr, "modes", modesStr)
+ slog.Debug("TUI launched", "app", appInfoStr, "modes", modesStr, "url", url)
go func() {
err = clipboard.Init()
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 9b6ec7ea6..0b87872fa 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -609,6 +609,15 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case "/tui/open-help":
helpDialog := dialog.NewHelpDialog(a.app)
a.modal = helpDialog
+ case "/tui/open-sessions":
+ sessionDialog := dialog.NewSessionDialog(a.app)
+ a.modal = sessionDialog
+ case "/tui/open-themes":
+ themeDialog := dialog.NewThemeDialog()
+ a.modal = themeDialog
+ case "/tui/open-models":
+ modelDialog := dialog.NewModelDialog(a.app)
+ a.modal = modelDialog
case "/tui/append-prompt":
var body struct {
Text string `json:"text"`
@@ -620,6 +629,34 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
text = " " + text
}
a.editor.SetValueWithAttachments(existing + text + " ")
+ case "/tui/submit-prompt":
+ updated, cmd := a.editor.Submit()
+ a.editor = updated.(chat.EditorComponent)
+ cmds = append(cmds, cmd)
+ case "/tui/clear-prompt":
+ updated, cmd := a.editor.Clear()
+ a.editor = updated.(chat.EditorComponent)
+ cmds = append(cmds, cmd)
+ case "/tui/execute-command":
+ var body struct {
+ Command string `json:"command"`
+ }
+ json.Unmarshal((msg.Body), &body)
+ command := commands.Command{}
+ for _, cmd := range a.app.Commands {
+ if string(cmd.Name) == body.Command {
+ command = cmd
+ break
+ }
+ }
+ if command.Name == "" {
+ slog.Error("Invalid command passed to /tui/execute-command", "command", body.Command)
+ return a, nil
+ }
+ updated, cmd := a.executeCommand(commands.Command(command))
+ a = updated.(Model)
+ cmds = append(cmds, cmd)
+
default:
break
}