gui/pkg/mcp/tools_layout.go
Snider 62ec735c10
Some checks failed
Security Scan / security (push) Has been cancelled
Test / test (push) Has been cancelled
refactor: AX compliance sweep — replace banned stdlib imports with core primitives
Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath,
errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim,
core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(),
core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives.

Framework boundary exceptions preserved where stdlib types are required
by external interfaces (Gin, net/http, CGo, Wails, bubbletea).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-13 09:32:01 +01:00

345 lines
12 KiB
Go

// pkg/mcp/tools_layout.go
package mcp
import (
"context"
corego "dappco.re/go/core"
"dappco.re/go/core/gui/pkg/screen"
"dappco.re/go/core/gui/pkg/window"
"forge.lthn.ai/core/go/pkg/core"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// --- layout_save ---
type LayoutSaveInput struct {
Name string `json:"name"`
}
type LayoutSaveOutput struct {
Success bool `json:"success"`
}
func (s *Subsystem) layoutSave(_ context.Context, _ *mcp.CallToolRequest, input LayoutSaveInput) (*mcp.CallToolResult, LayoutSaveOutput, error) {
_, _, err := s.core.PERFORM(window.TaskSaveLayout{Name: input.Name})
if err != nil {
return nil, LayoutSaveOutput{}, err
}
return nil, LayoutSaveOutput{Success: true}, nil
}
// --- layout_restore ---
type LayoutRestoreInput struct {
Name string `json:"name"`
}
type LayoutRestoreOutput struct {
Success bool `json:"success"`
}
func (s *Subsystem) layoutRestore(_ context.Context, _ *mcp.CallToolRequest, input LayoutRestoreInput) (*mcp.CallToolResult, LayoutRestoreOutput, error) {
_, _, err := s.core.PERFORM(window.TaskRestoreLayout{Name: input.Name})
if err != nil {
return nil, LayoutRestoreOutput{}, err
}
return nil, LayoutRestoreOutput{Success: true}, nil
}
// --- layout_list ---
type LayoutListInput struct{}
type LayoutListOutput struct {
Layouts []window.LayoutInfo `json:"layouts"`
}
func (s *Subsystem) layoutList(_ context.Context, _ *mcp.CallToolRequest, _ LayoutListInput) (*mcp.CallToolResult, LayoutListOutput, error) {
result, _, err := s.core.QUERY(window.QueryLayoutList{})
if err != nil {
return nil, LayoutListOutput{}, err
}
layouts, ok := result.([]window.LayoutInfo)
if !ok {
return nil, LayoutListOutput{}, corego.E("mcp.layout", "unexpected result type from layout list query", nil)
}
return nil, LayoutListOutput{Layouts: layouts}, nil
}
// --- layout_delete ---
type LayoutDeleteInput struct {
Name string `json:"name"`
}
type LayoutDeleteOutput struct {
Success bool `json:"success"`
}
func (s *Subsystem) layoutDelete(_ context.Context, _ *mcp.CallToolRequest, input LayoutDeleteInput) (*mcp.CallToolResult, LayoutDeleteOutput, error) {
_, _, err := s.core.PERFORM(window.TaskDeleteLayout{Name: input.Name})
if err != nil {
return nil, LayoutDeleteOutput{}, err
}
return nil, LayoutDeleteOutput{Success: true}, nil
}
// --- layout_get ---
type LayoutGetInput struct {
Name string `json:"name"`
}
type LayoutGetOutput struct {
Layout *window.Layout `json:"layout"`
}
func (s *Subsystem) layoutGet(_ context.Context, _ *mcp.CallToolRequest, input LayoutGetInput) (*mcp.CallToolResult, LayoutGetOutput, error) {
result, _, err := s.core.QUERY(window.QueryLayoutGet{Name: input.Name})
if err != nil {
return nil, LayoutGetOutput{}, err
}
layout, ok := result.(*window.Layout)
if !ok {
return nil, LayoutGetOutput{}, corego.E("mcp.layout", "unexpected result type from layout get query", nil)
}
return nil, LayoutGetOutput{Layout: layout}, nil
}
// --- layout_tile ---
type LayoutTileInput struct {
Mode string `json:"mode"`
Windows []string `json:"windows,omitempty"`
}
type LayoutTileOutput struct {
Success bool `json:"success"`
}
func (s *Subsystem) layoutTile(_ context.Context, _ *mcp.CallToolRequest, input LayoutTileInput) (*mcp.CallToolResult, LayoutTileOutput, error) {
_, _, err := s.core.PERFORM(window.TaskTileWindows{Mode: input.Mode, Windows: input.Windows})
if err != nil {
return nil, LayoutTileOutput{}, err
}
return nil, LayoutTileOutput{Success: true}, nil
}
// --- layout_snap ---
type LayoutSnapInput struct {
Name string `json:"name"`
Position string `json:"position"`
}
type LayoutSnapOutput struct {
Success bool `json:"success"`
}
func (s *Subsystem) layoutSnap(_ context.Context, _ *mcp.CallToolRequest, input LayoutSnapInput) (*mcp.CallToolResult, LayoutSnapOutput, error) {
_, _, err := s.core.PERFORM(window.TaskSnapWindow{Name: input.Name, Position: input.Position})
if err != nil {
return nil, LayoutSnapOutput{}, err
}
return nil, LayoutSnapOutput{Success: true}, nil
}
// --- layout_beside_editor ---
type LayoutBesideEditorInput struct {
Editor string `json:"editor,omitempty"`
Window string `json:"window,omitempty"`
}
type LayoutBesideEditorOutput struct {
Success bool `json:"success"`
}
func (s *Subsystem) layoutBesideEditor(_ context.Context, _ *mcp.CallToolRequest, input LayoutBesideEditorInput) (*mcp.CallToolResult, LayoutBesideEditorOutput, error) {
_, _, err := s.core.PERFORM(window.TaskBesideEditor{Editor: input.Editor, Window: input.Window})
if err != nil {
return nil, LayoutBesideEditorOutput{}, err
}
return nil, LayoutBesideEditorOutput{Success: true}, nil
}
// --- layout_suggest ---
type LayoutSuggestInput struct {
WindowCount int `json:"windowCount,omitempty"`
ScreenWidth int `json:"screenWidth,omitempty"`
ScreenHeight int `json:"screenHeight,omitempty"`
}
type LayoutSuggestOutput struct {
Suggestion window.LayoutSuggestion `json:"suggestion"`
}
func (s *Subsystem) layoutSuggest(_ context.Context, _ *mcp.CallToolRequest, input LayoutSuggestInput) (*mcp.CallToolResult, LayoutSuggestOutput, error) {
windowCount := input.WindowCount
if windowCount <= 0 {
result, _, err := s.core.QUERY(window.QueryWindowList{})
if err != nil {
return nil, LayoutSuggestOutput{}, err
}
windows, ok := result.([]window.WindowInfo)
if !ok {
return nil, LayoutSuggestOutput{}, corego.E("mcp.layout", "unexpected result type from window list query", nil)
}
windowCount = len(windows)
}
screenW, screenH := input.ScreenWidth, input.ScreenHeight
if screenW <= 0 || screenH <= 0 {
screenW, screenH = primaryScreenSize(s.core)
}
result, handled, err := s.core.QUERY(window.QueryLayoutSuggestion{
WindowCount: windowCount,
ScreenWidth: screenW,
ScreenHeight: screenH,
})
if err != nil {
return nil, LayoutSuggestOutput{}, err
}
if !handled {
return nil, LayoutSuggestOutput{}, corego.E("mcp.layout", "window service not available", nil)
}
suggestion, ok := result.(window.LayoutSuggestion)
if !ok {
return nil, LayoutSuggestOutput{}, corego.E("mcp.layout", "unexpected result type from layout suggestion query", nil)
}
return nil, LayoutSuggestOutput{Suggestion: suggestion}, nil
}
// --- screen_find_space ---
type ScreenFindSpaceInput struct {
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
}
type ScreenFindSpaceOutput struct {
Space window.SpaceInfo `json:"space"`
}
func (s *Subsystem) screenFindSpace(_ context.Context, _ *mcp.CallToolRequest, input ScreenFindSpaceInput) (*mcp.CallToolResult, ScreenFindSpaceOutput, error) {
screenW, screenH := primaryScreenSize(s.core)
if screenW <= 0 || screenH <= 0 {
screenW, screenH = 1920, 1080
}
result, handled, err := s.core.QUERY(window.QueryFindSpace{
Width: input.Width,
Height: input.Height,
ScreenWidth: screenW,
ScreenHeight: screenH,
})
if err != nil {
return nil, ScreenFindSpaceOutput{}, err
}
if !handled {
return nil, ScreenFindSpaceOutput{}, corego.E("mcp.layout", "window service not available", nil)
}
space, ok := result.(window.SpaceInfo)
if !ok {
return nil, ScreenFindSpaceOutput{}, corego.E("mcp.layout", "unexpected result type from find space query", nil)
}
if space.ScreenWidth == 0 {
space.ScreenWidth = screenW
}
if space.ScreenHeight == 0 {
space.ScreenHeight = screenH
}
return nil, ScreenFindSpaceOutput{Space: space}, nil
}
// --- window_arrange_pair ---
type WindowArrangePairInput struct {
First string `json:"first"`
Second string `json:"second"`
}
type WindowArrangePairOutput struct {
Success bool `json:"success"`
}
func (s *Subsystem) windowArrangePair(_ context.Context, _ *mcp.CallToolRequest, input WindowArrangePairInput) (*mcp.CallToolResult, WindowArrangePairOutput, error) {
_, _, err := s.core.PERFORM(window.TaskArrangePair{First: input.First, Second: input.Second})
if err != nil {
return nil, WindowArrangePairOutput{}, err
}
return nil, WindowArrangePairOutput{Success: true}, nil
}
// --- layout_stack ---
type LayoutStackInput struct {
Windows []string `json:"windows,omitempty"`
OffsetX int `json:"offsetX,omitempty"`
OffsetY int `json:"offsetY,omitempty"`
}
type LayoutStackOutput struct {
Success bool `json:"success"`
}
func (s *Subsystem) layoutStack(_ context.Context, _ *mcp.CallToolRequest, input LayoutStackInput) (*mcp.CallToolResult, LayoutStackOutput, error) {
_, _, err := s.core.PERFORM(window.TaskStackWindows{
Windows: input.Windows,
OffsetX: input.OffsetX,
OffsetY: input.OffsetY,
})
if err != nil {
return nil, LayoutStackOutput{}, err
}
return nil, LayoutStackOutput{Success: true}, nil
}
// --- layout_workflow ---
type LayoutWorkflowInput struct {
Workflow string `json:"workflow"`
Windows []string `json:"windows,omitempty"`
}
type LayoutWorkflowOutput struct {
Success bool `json:"success"`
}
func (s *Subsystem) layoutWorkflow(_ context.Context, _ *mcp.CallToolRequest, input LayoutWorkflowInput) (*mcp.CallToolResult, LayoutWorkflowOutput, error) {
workflow, ok := window.ParseWorkflowLayout(input.Workflow)
if !ok {
return nil, LayoutWorkflowOutput{}, corego.E("mcp.layout", corego.Sprintf("unknown workflow: %s", input.Workflow), nil)
}
_, _, err := s.core.PERFORM(window.TaskApplyWorkflow{
Workflow: workflow,
Windows: input.Windows,
})
if err != nil {
return nil, LayoutWorkflowOutput{}, err
}
return nil, LayoutWorkflowOutput{Success: true}, nil
}
// --- Registration ---
func (s *Subsystem) registerLayoutTools(server *mcp.Server) {
mcp.AddTool(server, &mcp.Tool{Name: "layout_save", Description: "Save the current window arrangement as a named layout"}, s.layoutSave)
mcp.AddTool(server, &mcp.Tool{Name: "layout_restore", Description: "Restore a saved window layout"}, s.layoutRestore)
mcp.AddTool(server, &mcp.Tool{Name: "layout_list", Description: "List all saved layouts"}, s.layoutList)
mcp.AddTool(server, &mcp.Tool{Name: "layout_delete", Description: "Delete a saved layout"}, s.layoutDelete)
mcp.AddTool(server, &mcp.Tool{Name: "layout_get", Description: "Get a specific layout by name"}, s.layoutGet)
mcp.AddTool(server, &mcp.Tool{Name: "layout_tile", Description: "Tile windows in a grid arrangement"}, s.layoutTile)
mcp.AddTool(server, &mcp.Tool{Name: "layout_snap", Description: "Snap a window to a screen edge or corner"}, s.layoutSnap)
mcp.AddTool(server, &mcp.Tool{Name: "layout_beside_editor", Description: "Place a window beside a detected editor window"}, s.layoutBesideEditor)
mcp.AddTool(server, &mcp.Tool{Name: "layout_suggest", Description: "Suggest an optimal layout for the current screen"}, s.layoutSuggest)
mcp.AddTool(server, &mcp.Tool{Name: "screen_find_space", Description: "Find an empty area for a new window"}, s.screenFindSpace)
mcp.AddTool(server, &mcp.Tool{Name: "window_arrange_pair", Description: "Arrange two windows side-by-side"}, s.windowArrangePair)
mcp.AddTool(server, &mcp.Tool{Name: "layout_stack", Description: "Cascade windows with an offset"}, s.layoutStack)
mcp.AddTool(server, &mcp.Tool{Name: "layout_workflow", Description: "Apply a predefined workflow layout"}, s.layoutWorkflow)
}
func primaryScreenSize(c *core.Core) (int, int) {
result, handled, err := c.QUERY(screen.QueryPrimary{})
if err == nil && handled {
if scr, ok := result.(*screen.Screen); ok && scr != nil {
if scr.WorkArea.Width > 0 && scr.WorkArea.Height > 0 {
return scr.WorkArea.Width, scr.WorkArea.Height
}
if scr.Bounds.Width > 0 && scr.Bounds.Height > 0 {
return scr.Bounds.Width, scr.Bounds.Height
}
if scr.Size.Width > 0 && scr.Size.Height > 0 {
return scr.Size.Width, scr.Size.Height
}
}
}
return 1920, 1080
}