Compare commits
2 commits
7bb3488f0e
...
47e11e7861
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47e11e7861 | ||
| 197ee400b1 |
997 changed files with 535 additions and 197510 deletions
BIN
cli
Executable file
BIN
cli
Executable file
Binary file not shown.
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/agentci"
|
"forge.lthn.ai/core/go/pkg/agentci"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/config"
|
"forge.lthn.ai/core/go/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddAgentCommands registers the 'agent' subcommand group under 'ai'.
|
// AddAgentCommands registers the 'agent' subcommand group under 'ai'.
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
package ai
|
package ai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases from shared package
|
// Style aliases from shared package
|
||||||
|
|
@ -13,9 +13,9 @@
|
||||||
package ai
|
package ai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
ragcmd "forge.lthn.ai/core/cli/internal/cmd/rag"
|
ragcmd "forge.lthn.ai/core/cli/cmd/rag"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -16,8 +16,8 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/log"
|
"forge.lthn.ai/core/go/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddDispatchCommands registers the 'dispatch' subcommand group under 'ai'.
|
// AddDispatchCommands registers the 'dispatch' subcommand group under 'ai'.
|
||||||
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/agentic"
|
"forge.lthn.ai/core/go/pkg/agentic"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// task:commit command flags
|
// task:commit command flags
|
||||||
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/ai"
|
"forge.lthn.ai/core/go/pkg/ai"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/config"
|
"forge.lthn.ai/core/go/pkg/config"
|
||||||
"forge.lthn.ai/core/cli/pkg/ratelimit"
|
"forge.lthn.ai/core/go/pkg/ratelimit"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddRateLimitCommands registers the 'ratelimits' subcommand group under 'ai'.
|
// AddRateLimitCommands registers the 'ratelimits' subcommand group under 'ai'.
|
||||||
|
|
@ -9,10 +9,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/agentic"
|
"forge.lthn.ai/core/go/pkg/agentic"
|
||||||
"forge.lthn.ai/core/cli/pkg/ai"
|
"forge.lthn.ai/core/go/pkg/ai"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// tasks command flags
|
// tasks command flags
|
||||||
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/agentic"
|
"forge.lthn.ai/core/go/pkg/agentic"
|
||||||
"forge.lthn.ai/core/cli/pkg/ai"
|
"forge.lthn.ai/core/go/pkg/ai"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// task:update command flags
|
// task:update command flags
|
||||||
|
|
@ -3,8 +3,8 @@ package ai
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/log"
|
"forge.lthn.ai/core/go/pkg/log"
|
||||||
"forge.lthn.ai/core/cli/pkg/ratelimit"
|
"forge.lthn.ai/core/go/pkg/ratelimit"
|
||||||
)
|
)
|
||||||
|
|
||||||
// executeWithRateLimit wraps an agent execution with rate limiting logic.
|
// executeWithRateLimit wraps an agent execution with rate limiting logic.
|
||||||
|
|
@ -17,8 +17,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/go/cmd/bugseti/icons"
|
"forge.lthn.ai/core/go/cmd/bugseti/icons"
|
||||||
"forge.lthn.ai/core/go/internal/bugseti"
|
"forge.lthn.ai/core/cli/internal/bugseti"
|
||||||
"forge.lthn.ai/core/go/internal/bugseti/updater"
|
"forge.lthn.ai/core/cli/internal/bugseti/updater"
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
"github.com/wailsapp/wails/v3/pkg/events"
|
"github.com/wailsapp/wails/v3/pkg/events"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"forge.lthn.ai/core/go/internal/bugseti"
|
"forge.lthn.ai/core/cli/internal/bugseti"
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/go/internal/bugseti"
|
"forge.lthn.ai/core/cli/internal/bugseti"
|
||||||
"forge.lthn.ai/core/go/pkg/io/datanode"
|
"forge.lthn.ai/core/go/pkg/io/datanode"
|
||||||
"github.com/Snider/Borg/pkg/tim"
|
"github.com/Snider/Borg/pkg/tim"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ package collect
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/collect"
|
"forge.lthn.ai/core/go/pkg/collect"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"forge.lthn.ai/core/cli/pkg/io"
|
"forge.lthn.ai/core/go/pkg/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/collect"
|
"forge.lthn.ai/core/go/pkg/collect"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BitcoinTalk command flags
|
// BitcoinTalk command flags
|
||||||
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
collectpkg "forge.lthn.ai/core/cli/pkg/collect"
|
collectpkg "forge.lthn.ai/core/go/pkg/collect"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addDispatchCommand adds the 'dispatch' subcommand to the collect parent.
|
// addDispatchCommand adds the 'dispatch' subcommand to the collect parent.
|
||||||
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/collect"
|
"forge.lthn.ai/core/go/pkg/collect"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Excavate command flags
|
// Excavate command flags
|
||||||
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/collect"
|
"forge.lthn.ai/core/go/pkg/collect"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GitHub command flags
|
// GitHub command flags
|
||||||
|
|
@ -3,9 +3,9 @@ package collect
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/collect"
|
"forge.lthn.ai/core/go/pkg/collect"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Market command flags
|
// Market command flags
|
||||||
|
|
@ -3,9 +3,9 @@ package collect
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/collect"
|
"forge.lthn.ai/core/go/pkg/collect"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Papers command flags
|
// Papers command flags
|
||||||
|
|
@ -3,9 +3,9 @@ package collect
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/collect"
|
"forge.lthn.ai/core/go/pkg/collect"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addProcessCommand adds the 'process' subcommand to the collect parent.
|
// addProcessCommand adds the 'process' subcommand to the collect parent.
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import "forge.lthn.ai/core/cli/pkg/cli"
|
import "forge.lthn.ai/core/go/pkg/cli"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cli.RegisterCommands(AddConfigCommands)
|
cli.RegisterCommands(AddConfigCommands)
|
||||||
|
|
@ -3,8 +3,8 @@ package config
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/config"
|
"forge.lthn.ai/core/go/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addGetCommand(parent *cli.Command) {
|
func addGetCommand(parent *cli.Command) {
|
||||||
|
|
@ -3,7 +3,7 @@ package config
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@ package config
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addPathCommand(parent *cli.Command) {
|
func addPathCommand(parent *cli.Command) {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addSetCommand(parent *cli.Command) {
|
func addSetCommand(parent *cli.Command) {
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"forge.lthn.ai/core/go/pkg/mcp/ide"
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BuildService provides build monitoring bindings for the frontend.
|
|
||||||
type BuildService struct {
|
|
||||||
ideSub *ide.Subsystem
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBuildService creates a new BuildService.
|
|
||||||
func NewBuildService(ideSub *ide.Subsystem) *BuildService {
|
|
||||||
return &BuildService{ideSub: ideSub}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceName returns the service name for Wails.
|
|
||||||
func (s *BuildService) ServiceName() string { return "BuildService" }
|
|
||||||
|
|
||||||
// ServiceStartup is called when the Wails application starts.
|
|
||||||
func (s *BuildService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
|
||||||
log.Println("BuildService started")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceShutdown is called when the Wails application shuts down.
|
|
||||||
func (s *BuildService) ServiceShutdown() error {
|
|
||||||
log.Println("BuildService shutdown")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildDTO is a build for the frontend.
|
|
||||||
type BuildDTO struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Repo string `json:"repo"`
|
|
||||||
Branch string `json:"branch"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Duration string `json:"duration,omitempty"`
|
|
||||||
StartedAt time.Time `json:"startedAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBuilds returns recent builds.
|
|
||||||
func (s *BuildService) GetBuilds(repo string) []BuildDTO {
|
|
||||||
bridge := s.ideSub.Bridge()
|
|
||||||
if bridge == nil {
|
|
||||||
return []BuildDTO{}
|
|
||||||
}
|
|
||||||
_ = bridge.Send(ide.BridgeMessage{
|
|
||||||
Type: "build_list",
|
|
||||||
Data: map[string]any{"repo": repo},
|
|
||||||
})
|
|
||||||
return []BuildDTO{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBuildLogs returns log output for a specific build.
|
|
||||||
func (s *BuildService) GetBuildLogs(buildID string) []string {
|
|
||||||
bridge := s.ideSub.Bridge()
|
|
||||||
if bridge == nil {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
_ = bridge.Send(ide.BridgeMessage{
|
|
||||||
Type: "build_logs",
|
|
||||||
Data: map[string]any{"buildId": buildID},
|
|
||||||
})
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"forge.lthn.ai/core/go/pkg/mcp/ide"
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ChatService provides chat bindings for the frontend.
|
|
||||||
type ChatService struct {
|
|
||||||
ideSub *ide.Subsystem
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewChatService creates a new ChatService.
|
|
||||||
func NewChatService(ideSub *ide.Subsystem) *ChatService {
|
|
||||||
return &ChatService{ideSub: ideSub}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceName returns the service name for Wails.
|
|
||||||
func (s *ChatService) ServiceName() string { return "ChatService" }
|
|
||||||
|
|
||||||
// ServiceStartup is called when the Wails application starts.
|
|
||||||
func (s *ChatService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
|
||||||
log.Println("ChatService started")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceShutdown is called when the Wails application shuts down.
|
|
||||||
func (s *ChatService) ServiceShutdown() error {
|
|
||||||
log.Println("ChatService shutdown")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChatMessageDTO is a message for the frontend.
|
|
||||||
type ChatMessageDTO struct {
|
|
||||||
Role string `json:"role"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Timestamp time.Time `json:"timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SessionDTO is a session for the frontend.
|
|
||||||
type SessionDTO struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlanStepDTO is a plan step for the frontend.
|
|
||||||
type PlanStepDTO struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlanDTO is a plan for the frontend.
|
|
||||||
type PlanDTO struct {
|
|
||||||
SessionID string `json:"sessionId"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Steps []PlanStepDTO `json:"steps"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendMessage sends a message to an agent session via the bridge.
|
|
||||||
func (s *ChatService) SendMessage(sessionID string, message string) (bool, error) {
|
|
||||||
bridge := s.ideSub.Bridge()
|
|
||||||
if bridge == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
err := bridge.Send(ide.BridgeMessage{
|
|
||||||
Type: "chat_send",
|
|
||||||
Channel: "chat:" + sessionID,
|
|
||||||
SessionID: sessionID,
|
|
||||||
Data: message,
|
|
||||||
})
|
|
||||||
return err == nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHistory retrieves message history for a session.
|
|
||||||
func (s *ChatService) GetHistory(sessionID string) []ChatMessageDTO {
|
|
||||||
bridge := s.ideSub.Bridge()
|
|
||||||
if bridge == nil {
|
|
||||||
return []ChatMessageDTO{}
|
|
||||||
}
|
|
||||||
_ = bridge.Send(ide.BridgeMessage{
|
|
||||||
Type: "chat_history",
|
|
||||||
SessionID: sessionID,
|
|
||||||
})
|
|
||||||
return []ChatMessageDTO{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListSessions returns active agent sessions.
|
|
||||||
func (s *ChatService) ListSessions() []SessionDTO {
|
|
||||||
bridge := s.ideSub.Bridge()
|
|
||||||
if bridge == nil {
|
|
||||||
return []SessionDTO{}
|
|
||||||
}
|
|
||||||
_ = bridge.Send(ide.BridgeMessage{Type: "session_list"})
|
|
||||||
return []SessionDTO{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateSession creates a new agent session.
|
|
||||||
func (s *ChatService) CreateSession(name string) SessionDTO {
|
|
||||||
bridge := s.ideSub.Bridge()
|
|
||||||
if bridge == nil {
|
|
||||||
return SessionDTO{Name: name, Status: "offline"}
|
|
||||||
}
|
|
||||||
_ = bridge.Send(ide.BridgeMessage{
|
|
||||||
Type: "session_create",
|
|
||||||
Data: map[string]any{"name": name},
|
|
||||||
})
|
|
||||||
return SessionDTO{
|
|
||||||
Name: name,
|
|
||||||
Status: "creating",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPlanStatus returns the plan status for a session.
|
|
||||||
func (s *ChatService) GetPlanStatus(sessionID string) PlanDTO {
|
|
||||||
bridge := s.ideSub.Bridge()
|
|
||||||
if bridge == nil {
|
|
||||||
return PlanDTO{SessionID: sessionID, Status: "offline"}
|
|
||||||
}
|
|
||||||
_ = bridge.Send(ide.BridgeMessage{
|
|
||||||
Type: "plan_status",
|
|
||||||
SessionID: sessionID,
|
|
||||||
})
|
|
||||||
return PlanDTO{
|
|
||||||
SessionID: sessionID,
|
|
||||||
Status: "unknown",
|
|
||||||
Steps: []PlanStepDTO{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
var wsUpgrader = websocket.Upgrader{
|
|
||||||
ReadBufferSize: 1024,
|
|
||||||
WriteBufferSize: 1024,
|
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClaudeBridge forwards messages between GUI clients and the MCP core WebSocket.
|
|
||||||
// This is the CLIENT bridge — it connects to the MCP core process on port 9876
|
|
||||||
// and relays messages bidirectionally with connected GUI WebSocket clients.
|
|
||||||
type ClaudeBridge struct {
|
|
||||||
mcpConn *websocket.Conn
|
|
||||||
mcpURL string
|
|
||||||
clients map[*websocket.Conn]bool
|
|
||||||
clientsMu sync.RWMutex
|
|
||||||
broadcast chan []byte
|
|
||||||
reconnectMu sync.Mutex
|
|
||||||
connected bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClaudeBridge creates a new bridge to the MCP core WebSocket.
|
|
||||||
func NewClaudeBridge(mcpURL string) *ClaudeBridge {
|
|
||||||
return &ClaudeBridge{
|
|
||||||
mcpURL: mcpURL,
|
|
||||||
clients: make(map[*websocket.Conn]bool),
|
|
||||||
broadcast: make(chan []byte, 256),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connected reports whether the bridge is connected to MCP core.
|
|
||||||
func (cb *ClaudeBridge) Connected() bool {
|
|
||||||
cb.reconnectMu.Lock()
|
|
||||||
defer cb.reconnectMu.Unlock()
|
|
||||||
return cb.connected
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start connects to the MCP WebSocket and starts the bridge.
|
|
||||||
func (cb *ClaudeBridge) Start() {
|
|
||||||
go cb.connectToMCP()
|
|
||||||
go cb.broadcastLoop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// connectToMCP establishes connection to the MCP core WebSocket.
|
|
||||||
func (cb *ClaudeBridge) connectToMCP() {
|
|
||||||
for {
|
|
||||||
cb.reconnectMu.Lock()
|
|
||||||
if cb.mcpConn != nil {
|
|
||||||
cb.mcpConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("ide bridge: connect to MCP at %s", cb.mcpURL)
|
|
||||||
conn, _, err := websocket.DefaultDialer.Dial(cb.mcpURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("ide bridge: connect failed: %v", err)
|
|
||||||
cb.connected = false
|
|
||||||
cb.reconnectMu.Unlock()
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cb.mcpConn = conn
|
|
||||||
cb.connected = true
|
|
||||||
cb.reconnectMu.Unlock()
|
|
||||||
log.Println("ide bridge: connected to MCP core")
|
|
||||||
|
|
||||||
// Read messages from MCP and broadcast to GUI clients
|
|
||||||
for {
|
|
||||||
_, message, err := conn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("ide bridge: MCP read error: %v", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cb.broadcast <- message
|
|
||||||
}
|
|
||||||
|
|
||||||
cb.reconnectMu.Lock()
|
|
||||||
cb.connected = false
|
|
||||||
cb.reconnectMu.Unlock()
|
|
||||||
|
|
||||||
// Connection lost, retry after delay
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// broadcastLoop sends messages from MCP core to all connected GUI clients.
|
|
||||||
func (cb *ClaudeBridge) broadcastLoop() {
|
|
||||||
for message := range cb.broadcast {
|
|
||||||
cb.clientsMu.RLock()
|
|
||||||
for client := range cb.clients {
|
|
||||||
if err := client.WriteMessage(websocket.TextMessage, message); err != nil {
|
|
||||||
log.Printf("ide bridge: client write error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cb.clientsMu.RUnlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleWebSocket handles WebSocket connections from GUI clients.
|
|
||||||
func (cb *ClaudeBridge) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
|
|
||||||
conn, err := wsUpgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("ide bridge: upgrade error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cb.clientsMu.Lock()
|
|
||||||
cb.clients[conn] = true
|
|
||||||
cb.clientsMu.Unlock()
|
|
||||||
|
|
||||||
// Send connected message
|
|
||||||
connMsg, _ := json.Marshal(map[string]any{
|
|
||||||
"type": "system",
|
|
||||||
"data": "Connected to Claude bridge",
|
|
||||||
"timestamp": time.Now(),
|
|
||||||
})
|
|
||||||
conn.WriteMessage(websocket.TextMessage, connMsg)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
cb.clientsMu.Lock()
|
|
||||||
delete(cb.clients, conn)
|
|
||||||
cb.clientsMu.Unlock()
|
|
||||||
conn.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Read messages from GUI client and forward to MCP core
|
|
||||||
for {
|
|
||||||
_, message, err := conn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the message to check type
|
|
||||||
var msg map[string]any
|
|
||||||
if err := json.Unmarshal(message, &msg); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward claude_message to MCP core
|
|
||||||
if msgType, ok := msg["type"].(string); ok && msgType == "claude_message" {
|
|
||||||
cb.sendToMCP(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendToMCP sends a message to the MCP WebSocket.
|
|
||||||
func (cb *ClaudeBridge) sendToMCP(message []byte) {
|
|
||||||
cb.reconnectMu.Lock()
|
|
||||||
defer cb.reconnectMu.Unlock()
|
|
||||||
|
|
||||||
if cb.mcpConn == nil {
|
|
||||||
log.Println("ide bridge: MCP not connected, dropping message")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cb.mcpConn.WriteMessage(websocket.TextMessage, message); err != nil {
|
|
||||||
log.Printf("ide bridge: MCP write error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
||||||
"version": 1,
|
|
||||||
"newProjectRoot": "projects",
|
|
||||||
"projects": {
|
|
||||||
"core-ide": {
|
|
||||||
"projectType": "application",
|
|
||||||
"schematics": {
|
|
||||||
"@schematics/angular:component": {
|
|
||||||
"style": "scss",
|
|
||||||
"standalone": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "",
|
|
||||||
"sourceRoot": "src",
|
|
||||||
"prefix": "app",
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@angular-devkit/build-angular:application",
|
|
||||||
"options": {
|
|
||||||
"outputPath": "dist/core-ide",
|
|
||||||
"index": "src/index.html",
|
|
||||||
"browser": "src/main.ts",
|
|
||||||
"polyfills": ["zone.js"],
|
|
||||||
"tsConfig": "tsconfig.app.json",
|
|
||||||
"inlineStyleLanguage": "scss",
|
|
||||||
"assets": [
|
|
||||||
"src/favicon.ico",
|
|
||||||
"src/assets"
|
|
||||||
],
|
|
||||||
"styles": [
|
|
||||||
"src/styles.scss"
|
|
||||||
],
|
|
||||||
"scripts": []
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"budgets": [
|
|
||||||
{
|
|
||||||
"type": "initial",
|
|
||||||
"maximumWarning": "500kb",
|
|
||||||
"maximumError": "1mb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "anyComponentStyle",
|
|
||||||
"maximumWarning": "2kb",
|
|
||||||
"maximumError": "4kb"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputHashing": "all"
|
|
||||||
},
|
|
||||||
"development": {
|
|
||||||
"optimization": false,
|
|
||||||
"extractLicenses": false,
|
|
||||||
"sourceMap": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultConfiguration": "production"
|
|
||||||
},
|
|
||||||
"serve": {
|
|
||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"buildTarget": "core-ide:build:production"
|
|
||||||
},
|
|
||||||
"development": {
|
|
||||||
"buildTarget": "core-ide:build:development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultConfiguration": "development"
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"polyfills": ["zone.js", "zone.js/testing"],
|
|
||||||
"tsConfig": "tsconfig.spec.json",
|
|
||||||
"inlineStyleLanguage": "scss",
|
|
||||||
"assets": [
|
|
||||||
"src/favicon.ico",
|
|
||||||
"src/assets"
|
|
||||||
],
|
|
||||||
"styles": [
|
|
||||||
"src/styles.scss"
|
|
||||||
],
|
|
||||||
"scripts": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
16159
cmd/core-ide/frontend/package-lock.json
generated
16159
cmd/core-ide/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"name": "core-ide",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"ng": "ng",
|
|
||||||
"start": "ng serve",
|
|
||||||
"dev": "ng serve --configuration development",
|
|
||||||
"build": "ng build --configuration production",
|
|
||||||
"build:dev": "ng build --configuration development",
|
|
||||||
"watch": "ng build --watch --configuration development",
|
|
||||||
"test": "ng test",
|
|
||||||
"lint": "ng lint"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@angular/animations": "^19.1.0",
|
|
||||||
"@angular/common": "^19.1.0",
|
|
||||||
"@angular/compiler": "^19.1.0",
|
|
||||||
"@angular/core": "^19.1.0",
|
|
||||||
"@angular/forms": "^19.1.0",
|
|
||||||
"@angular/platform-browser": "^19.1.0",
|
|
||||||
"@angular/platform-browser-dynamic": "^19.1.0",
|
|
||||||
"@angular/router": "^19.1.0",
|
|
||||||
"rxjs": "~7.8.0",
|
|
||||||
"tslib": "^2.3.0",
|
|
||||||
"zone.js": "~0.15.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@angular-devkit/build-angular": "^19.1.0",
|
|
||||||
"@angular/cli": "^21.1.2",
|
|
||||||
"@angular/compiler-cli": "^19.1.0",
|
|
||||||
"@types/jasmine": "~5.1.0",
|
|
||||||
"jasmine-core": "~5.1.0",
|
|
||||||
"karma": "~6.4.0",
|
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
|
||||||
"karma-coverage": "~2.2.0",
|
|
||||||
"karma-jasmine": "~5.1.0",
|
|
||||||
"karma-jasmine-html-reporter": "~2.1.0",
|
|
||||||
"typescript": "~5.5.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { RouterOutlet } from '@angular/router';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-root',
|
|
||||||
standalone: true,
|
|
||||||
imports: [RouterOutlet],
|
|
||||||
template: '<router-outlet></router-outlet>',
|
|
||||||
styles: [`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
`]
|
|
||||||
})
|
|
||||||
export class AppComponent {
|
|
||||||
title = 'Core IDE';
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import { ApplicationConfig } from '@angular/core';
|
|
||||||
import { provideRouter } from '@angular/router';
|
|
||||||
import { routes } from './app.routes';
|
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
|
||||||
providers: [
|
|
||||||
provideRouter(routes)
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import { Routes } from '@angular/router';
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
redirectTo: 'tray',
|
|
||||||
pathMatch: 'full'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'tray',
|
|
||||||
loadComponent: () => import('./tray/tray.component').then(m => m.TrayComponent)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'main',
|
|
||||||
loadComponent: () => import('./main/main.component').then(m => m.MainComponent)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'settings',
|
|
||||||
loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'jellyfin',
|
|
||||||
loadComponent: () => import('./jellyfin/jellyfin.component').then(m => m.JellyfinComponent)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { WailsService, Build } from '@shared/wails.service';
|
|
||||||
import { WebSocketService, WSMessage } from '@shared/ws.service';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-build',
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule],
|
|
||||||
template: `
|
|
||||||
<div class="builds">
|
|
||||||
<div class="builds__header">
|
|
||||||
<h2>Builds</h2>
|
|
||||||
<button class="btn btn--secondary" (click)="refresh()">Refresh</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="builds__list">
|
|
||||||
<div
|
|
||||||
*ngFor="let build of builds; trackBy: trackBuild"
|
|
||||||
class="build-card"
|
|
||||||
[class.build-card--expanded]="expandedId === build.id"
|
|
||||||
(click)="toggle(build.id)"
|
|
||||||
>
|
|
||||||
<div class="build-card__header">
|
|
||||||
<div class="build-card__info">
|
|
||||||
<span class="build-card__repo">{{ build.repo }}</span>
|
|
||||||
<span class="build-card__branch text-muted">{{ build.branch }}</span>
|
|
||||||
</div>
|
|
||||||
<span class="badge" [class]="statusBadge(build.status)">{{ build.status }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="build-card__meta text-muted">
|
|
||||||
{{ build.startedAt | date:'medium' }}
|
|
||||||
<span *ngIf="build.duration"> · {{ build.duration }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="expandedId === build.id" class="build-card__logs">
|
|
||||||
<pre *ngIf="logs.length > 0">{{ logs.join('\\n') }}</pre>
|
|
||||||
<p *ngIf="logs.length === 0" class="text-muted">No logs available</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="builds.length === 0" class="builds__empty text-muted">
|
|
||||||
No builds found. Builds will appear here from Forgejo CI.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
styles: [`
|
|
||||||
.builds {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.builds__header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.builds__list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.build-card {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--text-muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.build-card__header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.build-card__info {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.build-card__repo {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.build-card__branch {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.build-card__meta {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.build-card__logs {
|
|
||||||
margin-top: var(--spacing-md);
|
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
padding-top: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.build-card__logs pre {
|
|
||||||
font-size: 12px;
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.builds__empty {
|
|
||||||
text-align: center;
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
`]
|
|
||||||
})
|
|
||||||
export class BuildComponent implements OnInit, OnDestroy {
|
|
||||||
builds: Build[] = [];
|
|
||||||
expandedId = '';
|
|
||||||
logs: string[] = [];
|
|
||||||
|
|
||||||
private sub: Subscription | null = null;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private wails: WailsService,
|
|
||||||
private wsService: WebSocketService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.refresh();
|
|
||||||
this.wsService.connect();
|
|
||||||
this.sub = this.wsService.subscribe('build:status').subscribe(
|
|
||||||
(msg: WSMessage) => {
|
|
||||||
if (msg.data && typeof msg.data === 'object') {
|
|
||||||
const update = msg.data as Build;
|
|
||||||
const idx = this.builds.findIndex(b => b.id === update.id);
|
|
||||||
if (idx >= 0) {
|
|
||||||
this.builds[idx] = { ...this.builds[idx], ...update };
|
|
||||||
} else {
|
|
||||||
this.builds.unshift(update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.sub?.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
async refresh(): Promise<void> {
|
|
||||||
this.builds = await this.wails.getBuilds();
|
|
||||||
}
|
|
||||||
|
|
||||||
async toggle(buildId: string): Promise<void> {
|
|
||||||
if (this.expandedId === buildId) {
|
|
||||||
this.expandedId = '';
|
|
||||||
this.logs = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.expandedId = buildId;
|
|
||||||
this.logs = await this.wails.getBuildLogs(buildId);
|
|
||||||
}
|
|
||||||
|
|
||||||
trackBuild(_: number, build: Build): string {
|
|
||||||
return build.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
statusBadge(status: string): string {
|
|
||||||
switch (status) {
|
|
||||||
case 'success': return 'badge--success';
|
|
||||||
case 'running': return 'badge--info';
|
|
||||||
case 'failed': return 'badge--danger';
|
|
||||||
default: return 'badge--warning';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,242 +0,0 @@
|
||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { WailsService, ChatMessage, Session, PlanStatus } from '@shared/wails.service';
|
|
||||||
import { WebSocketService, WSMessage } from '@shared/ws.service';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-chat',
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule, FormsModule],
|
|
||||||
template: `
|
|
||||||
<div class="chat">
|
|
||||||
<div class="chat__header">
|
|
||||||
<div class="chat__session-picker">
|
|
||||||
<select class="form-select" [(ngModel)]="activeSessionId" (ngModelChange)="onSessionChange()">
|
|
||||||
<option *ngFor="let s of sessions" [value]="s.id">{{ s.name }} ({{ s.status }})</option>
|
|
||||||
</select>
|
|
||||||
<button class="btn btn--ghost" (click)="createSession()">+ New</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chat__body">
|
|
||||||
<div class="chat__messages">
|
|
||||||
<div
|
|
||||||
*ngFor="let msg of messages"
|
|
||||||
class="chat__msg"
|
|
||||||
[class.chat__msg--user]="msg.role === 'user'"
|
|
||||||
[class.chat__msg--agent]="msg.role === 'agent'"
|
|
||||||
>
|
|
||||||
<div class="chat__msg-role">{{ msg.role }}</div>
|
|
||||||
<div class="chat__msg-content">{{ msg.content }}</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="messages.length === 0" class="chat__empty text-muted">
|
|
||||||
No messages yet. Start a conversation with an agent.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="plan.steps.length > 0" class="chat__plan">
|
|
||||||
<h4>Plan: {{ plan.status }}</h4>
|
|
||||||
<ul>
|
|
||||||
<li *ngFor="let step of plan.steps" [class]="'plan-step plan-step--' + step.status">
|
|
||||||
{{ step.name }}
|
|
||||||
<span class="badge badge--info">{{ step.status }}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chat__input">
|
|
||||||
<textarea
|
|
||||||
class="form-textarea"
|
|
||||||
[(ngModel)]="draft"
|
|
||||||
(keydown.enter)="sendMessage($any($event))"
|
|
||||||
placeholder="Type a message... (Enter to send)"
|
|
||||||
rows="2"
|
|
||||||
></textarea>
|
|
||||||
<button class="btn btn--primary" (click)="sendMessage()" [disabled]="!draft.trim()">Send</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
styles: [`
|
|
||||||
.chat {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__header {
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__session-picker {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__session-picker select {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__body {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__messages {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__msg {
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
max-width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__msg--user {
|
|
||||||
align-self: flex-end;
|
|
||||||
background: rgba(57, 208, 216, 0.12);
|
|
||||||
border: 1px solid rgba(57, 208, 216, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__msg--agent {
|
|
||||||
align-self: flex-start;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__msg-role {
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__msg-content {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__empty {
|
|
||||||
margin: auto;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__plan {
|
|
||||||
width: 260px;
|
|
||||||
border-left: 1px solid var(--border-color);
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__plan ul {
|
|
||||||
list-style: none;
|
|
||||||
margin-top: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__plan li {
|
|
||||||
padding: var(--spacing-xs) 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__input {
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat__input textarea {
|
|
||||||
flex: 1;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
`]
|
|
||||||
})
|
|
||||||
export class ChatComponent implements OnInit, OnDestroy {
|
|
||||||
sessions: Session[] = [];
|
|
||||||
activeSessionId = '';
|
|
||||||
messages: ChatMessage[] = [];
|
|
||||||
plan: PlanStatus = { sessionId: '', status: '', steps: [] };
|
|
||||||
draft = '';
|
|
||||||
|
|
||||||
private sub: Subscription | null = null;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private wails: WailsService,
|
|
||||||
private wsService: WebSocketService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.loadSessions();
|
|
||||||
this.wsService.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.sub?.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadSessions(): Promise<void> {
|
|
||||||
this.sessions = await this.wails.listSessions();
|
|
||||||
if (this.sessions.length > 0 && !this.activeSessionId) {
|
|
||||||
this.activeSessionId = this.sessions[0].id;
|
|
||||||
this.onSessionChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async onSessionChange(): Promise<void> {
|
|
||||||
if (!this.activeSessionId) return;
|
|
||||||
|
|
||||||
// Unsubscribe from previous channel
|
|
||||||
this.sub?.unsubscribe();
|
|
||||||
|
|
||||||
// Load history and plan
|
|
||||||
this.messages = await this.wails.getHistory(this.activeSessionId);
|
|
||||||
this.plan = await this.wails.getPlanStatus(this.activeSessionId);
|
|
||||||
|
|
||||||
// Subscribe to live updates
|
|
||||||
this.sub = this.wsService.subscribe(`chat:${this.activeSessionId}`).subscribe(
|
|
||||||
(msg: WSMessage) => {
|
|
||||||
if (msg.data && typeof msg.data === 'object') {
|
|
||||||
this.messages.push(msg.data as ChatMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendMessage(event?: KeyboardEvent): Promise<void> {
|
|
||||||
if (event) {
|
|
||||||
if (event.shiftKey) return; // Allow shift+enter for newlines
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
const text = this.draft.trim();
|
|
||||||
if (!text || !this.activeSessionId) return;
|
|
||||||
|
|
||||||
// Optimistic UI update
|
|
||||||
this.messages.push({ role: 'user', content: text, timestamp: new Date().toISOString() });
|
|
||||||
this.draft = '';
|
|
||||||
|
|
||||||
await this.wails.sendMessage(this.activeSessionId, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
async createSession(): Promise<void> {
|
|
||||||
const name = `Session ${this.sessions.length + 1}`;
|
|
||||||
const session = await this.wails.createSession(name);
|
|
||||||
this.sessions.push(session);
|
|
||||||
this.activeSessionId = session.id;
|
|
||||||
this.onSessionChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { WailsService, DashboardData } from '@shared/wails.service';
|
|
||||||
import { WebSocketService, WSMessage } from '@shared/ws.service';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
interface ActivityItem {
|
|
||||||
type: string;
|
|
||||||
message: string;
|
|
||||||
timestamp: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-dashboard',
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule],
|
|
||||||
template: `
|
|
||||||
<div class="dashboard">
|
|
||||||
<h2>Dashboard</h2>
|
|
||||||
|
|
||||||
<div class="dashboard__grid">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-card__value" [class.text-success]="data.connection.bridgeConnected">
|
|
||||||
{{ data.connection.bridgeConnected ? 'Online' : 'Offline' }}
|
|
||||||
</div>
|
|
||||||
<div class="stat-card__label">Bridge Status</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-card__value">{{ data.connection.wsClients }}</div>
|
|
||||||
<div class="stat-card__label">WS Clients</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-card__value">{{ data.connection.wsChannels }}</div>
|
|
||||||
<div class="stat-card__label">Active Channels</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-card__value">0</div>
|
|
||||||
<div class="stat-card__label">Agent Sessions</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dashboard__activity">
|
|
||||||
<h3>Activity Feed</h3>
|
|
||||||
<div class="activity-feed">
|
|
||||||
<div *ngFor="let item of activity" class="activity-item">
|
|
||||||
<span class="activity-item__badge badge badge--info">{{ item.type }}</span>
|
|
||||||
<span class="activity-item__msg">{{ item.message }}</span>
|
|
||||||
<span class="activity-item__time text-muted">{{ item.timestamp | date:'shortTime' }}</span>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="activity.length === 0" class="text-muted" style="text-align: center; padding: var(--spacing-lg);">
|
|
||||||
No recent activity. Events will stream here in real-time.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
styles: [`
|
|
||||||
.dashboard {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard__grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
margin: var(--spacing-md) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card__value {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--accent-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card__label {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-top: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard__activity {
|
|
||||||
margin-top: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-feed {
|
|
||||||
margin-top: var(--spacing-sm);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-item__msg {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-item__time {
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
`]
|
|
||||||
})
|
|
||||||
export class DashboardComponent implements OnInit, OnDestroy {
|
|
||||||
data: DashboardData = {
|
|
||||||
connection: { bridgeConnected: false, laravelUrl: '', wsClients: 0, wsChannels: 0 }
|
|
||||||
};
|
|
||||||
activity: ActivityItem[] = [];
|
|
||||||
|
|
||||||
private sub: Subscription | null = null;
|
|
||||||
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private wails: WailsService,
|
|
||||||
private wsService: WebSocketService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.refresh();
|
|
||||||
this.pollTimer = setInterval(() => this.refresh(), 10000);
|
|
||||||
|
|
||||||
this.wsService.connect();
|
|
||||||
this.sub = this.wsService.subscribe('dashboard:activity').subscribe(
|
|
||||||
(msg: WSMessage) => {
|
|
||||||
if (msg.data && typeof msg.data === 'object') {
|
|
||||||
this.activity.unshift(msg.data as ActivityItem);
|
|
||||||
if (this.activity.length > 100) {
|
|
||||||
this.activity.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.sub?.unsubscribe();
|
|
||||||
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
async refresh(): Promise<void> {
|
|
||||||
this.data = await this.wails.getDashboard();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
|
||||||
|
|
||||||
type Mode = 'web' | 'stream';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-jellyfin',
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule, FormsModule],
|
|
||||||
template: `
|
|
||||||
<div class="jellyfin">
|
|
||||||
<header class="jellyfin__header">
|
|
||||||
<div>
|
|
||||||
<h2>Jellyfin Player</h2>
|
|
||||||
<p class="text-muted">Embedded media access for Host UK workflows.</p>
|
|
||||||
</div>
|
|
||||||
<div class="mode-switch">
|
|
||||||
<button class="btn btn--secondary" [class.is-active]="mode === 'web'" (click)="mode = 'web'">Web</button>
|
|
||||||
<button class="btn btn--secondary" [class.is-active]="mode === 'stream'" (click)="mode = 'stream'">Stream</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<section class="card">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">Jellyfin Server URL</label>
|
|
||||||
<input class="form-input" [(ngModel)]="serverUrl" placeholder="https://media.lthn.ai" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="mode === 'stream'" class="stream-grid">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">Item ID</label>
|
|
||||||
<input class="form-input" [(ngModel)]="itemId" placeholder="Jellyfin library item ID" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">API Key</label>
|
|
||||||
<input class="form-input" [(ngModel)]="apiKey" placeholder="Jellyfin API key" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">Media Source ID (optional)</label>
|
|
||||||
<input class="form-input" [(ngModel)]="mediaSourceId" placeholder="Source ID for multi-source items" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<button class="btn btn--primary" (click)="load()">Load Player</button>
|
|
||||||
<button class="btn btn--secondary" (click)="reset()">Reset</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="card viewer" *ngIf="loaded && mode === 'web'">
|
|
||||||
<iframe
|
|
||||||
class="jellyfin-frame"
|
|
||||||
title="Jellyfin Web"
|
|
||||||
[src]="safeWebUrl"
|
|
||||||
loading="lazy"
|
|
||||||
referrerpolicy="no-referrer"
|
|
||||||
></iframe>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="card viewer" *ngIf="loaded && mode === 'stream'">
|
|
||||||
<video class="jellyfin-video" controls [src]="streamUrl"></video>
|
|
||||||
<p class="text-muted stream-hint" *ngIf="!streamUrl">Set Item ID and API key to build stream URL.</p>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
styles: [`
|
|
||||||
.jellyfin {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
min-height: 100%;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.jellyfin__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mode-switch {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mode-switch .btn.is-active {
|
|
||||||
border-color: var(--accent-primary);
|
|
||||||
color: var(--accent-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stream-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewer {
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
min-height: 520px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jellyfin-frame,
|
|
||||||
.jellyfin-video {
|
|
||||||
border: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
min-height: 520px;
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stream-hint {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
`]
|
|
||||||
})
|
|
||||||
export class JellyfinComponent {
|
|
||||||
mode: Mode = 'web';
|
|
||||||
loaded = false;
|
|
||||||
|
|
||||||
serverUrl = 'https://media.lthn.ai';
|
|
||||||
itemId = '';
|
|
||||||
apiKey = '';
|
|
||||||
mediaSourceId = '';
|
|
||||||
|
|
||||||
safeWebUrl!: SafeResourceUrl;
|
|
||||||
streamUrl = '';
|
|
||||||
|
|
||||||
constructor(private sanitizer: DomSanitizer) {
|
|
||||||
this.safeWebUrl = this.sanitizer.bypassSecurityTrustResourceUrl('https://media.lthn.ai/web/index.html');
|
|
||||||
}
|
|
||||||
|
|
||||||
load(): void {
|
|
||||||
const base = this.normalizeBase(this.serverUrl);
|
|
||||||
this.safeWebUrl = this.sanitizer.bypassSecurityTrustResourceUrl(`${base}/web/index.html`);
|
|
||||||
this.streamUrl = this.buildStreamUrl(base);
|
|
||||||
this.loaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(): void {
|
|
||||||
this.loaded = false;
|
|
||||||
this.itemId = '';
|
|
||||||
this.apiKey = '';
|
|
||||||
this.mediaSourceId = '';
|
|
||||||
this.streamUrl = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
private normalizeBase(value: string): string {
|
|
||||||
const raw = value.trim() || 'https://media.lthn.ai';
|
|
||||||
const withProtocol = raw.startsWith('http://') || raw.startsWith('https://') ? raw : `https://${raw}`;
|
|
||||||
return withProtocol.replace(/\/+$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildStreamUrl(base: string): string {
|
|
||||||
if (!this.itemId.trim() || !this.apiKey.trim()) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = new URL(`${base}/Videos/${encodeURIComponent(this.itemId.trim())}/stream`);
|
|
||||||
url.searchParams.set('api_key', this.apiKey.trim());
|
|
||||||
url.searchParams.set('static', 'true');
|
|
||||||
if (this.mediaSourceId.trim()) {
|
|
||||||
url.searchParams.set('MediaSourceId', this.mediaSourceId.trim());
|
|
||||||
}
|
|
||||||
return url.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { ChatComponent } from '../chat/chat.component';
|
|
||||||
import { BuildComponent } from '../build/build.component';
|
|
||||||
import { DashboardComponent } from '../dashboard/dashboard.component';
|
|
||||||
import { JellyfinComponent } from '../jellyfin/jellyfin.component';
|
|
||||||
|
|
||||||
type Panel = 'chat' | 'build' | 'dashboard' | 'jellyfin';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-main',
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule, ChatComponent, BuildComponent, DashboardComponent, JellyfinComponent],
|
|
||||||
template: `
|
|
||||||
<div class="ide">
|
|
||||||
<nav class="ide__sidebar">
|
|
||||||
<div class="ide__logo">Core IDE</div>
|
|
||||||
<ul class="ide__nav">
|
|
||||||
<li
|
|
||||||
*ngFor="let item of navItems"
|
|
||||||
class="ide__nav-item"
|
|
||||||
[class.active]="activePanel === item.id"
|
|
||||||
(click)="activePanel = item.id"
|
|
||||||
>
|
|
||||||
<span class="ide__nav-icon">{{ item.icon }}</span>
|
|
||||||
<span class="ide__nav-label">{{ item.label }}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="ide__nav-footer text-muted">v0.1.0</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main class="ide__content">
|
|
||||||
<app-chat *ngIf="activePanel === 'chat'" />
|
|
||||||
<app-build *ngIf="activePanel === 'build'" />
|
|
||||||
<app-dashboard *ngIf="activePanel === 'dashboard'" />
|
|
||||||
<app-jellyfin *ngIf="activePanel === 'jellyfin'" />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
styles: [`
|
|
||||||
.ide {
|
|
||||||
display: flex;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ide__sidebar {
|
|
||||||
width: var(--sidebar-width);
|
|
||||||
background: var(--bg-sidebar);
|
|
||||||
border-right: 1px solid var(--border-color);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: var(--spacing-md) 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ide__logo {
|
|
||||||
padding: 0 var(--spacing-md);
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--accent-primary);
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ide__nav {
|
|
||||||
list-style: none;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ide__nav-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
transition: all 0.15s;
|
|
||||||
border-left: 3px solid transparent;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--text-primary);
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: var(--accent-primary);
|
|
||||||
background: rgba(57, 208, 216, 0.08);
|
|
||||||
border-left-color: var(--accent-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ide__nav-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
width: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ide__nav-footer {
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ide__content {
|
|
||||||
flex: 1;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
`]
|
|
||||||
})
|
|
||||||
export class MainComponent {
|
|
||||||
activePanel: Panel = 'dashboard';
|
|
||||||
|
|
||||||
navItems: { id: Panel; label: string; icon: string }[] = [
|
|
||||||
{ id: 'dashboard', label: 'Dashboard', icon: '\u25A6' },
|
|
||||||
{ id: 'chat', label: 'Chat', icon: '\u2709' },
|
|
||||||
{ id: 'build', label: 'Builds', icon: '\u2699' },
|
|
||||||
{ id: 'jellyfin', label: 'Jellyfin', icon: '\u25B6' },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-settings',
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule, FormsModule],
|
|
||||||
template: `
|
|
||||||
<div class="settings">
|
|
||||||
<h2>Settings</h2>
|
|
||||||
|
|
||||||
<div class="settings__section">
|
|
||||||
<h3>Connection</h3>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">Laravel WebSocket URL</label>
|
|
||||||
<input
|
|
||||||
class="form-input"
|
|
||||||
[(ngModel)]="laravelUrl"
|
|
||||||
placeholder="ws://localhost:9876/ws"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">Workspace Root</label>
|
|
||||||
<input
|
|
||||||
class="form-input"
|
|
||||||
[(ngModel)]="workspaceRoot"
|
|
||||||
placeholder="/path/to/workspace"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings__section">
|
|
||||||
<h3>Appearance</h3>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">Theme</label>
|
|
||||||
<select class="form-select" [(ngModel)]="theme">
|
|
||||||
<option value="dark">Dark</option>
|
|
||||||
<option value="light">Light</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings__actions">
|
|
||||||
<button class="btn btn--primary" (click)="save()">Save Settings</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
styles: [`
|
|
||||||
.settings {
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings__section {
|
|
||||||
margin-top: var(--spacing-lg);
|
|
||||||
padding-top: var(--spacing-lg);
|
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
margin-top: var(--spacing-md);
|
|
||||||
padding-top: 0;
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings__actions {
|
|
||||||
margin-top: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
`]
|
|
||||||
})
|
|
||||||
export class SettingsComponent implements OnInit {
|
|
||||||
laravelUrl = 'ws://localhost:9876/ws';
|
|
||||||
workspaceRoot = '.';
|
|
||||||
theme = 'dark';
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
// Settings will be loaded from the Go backend
|
|
||||||
const saved = localStorage.getItem('ide-settings');
|
|
||||||
if (saved) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(saved);
|
|
||||||
this.laravelUrl = parsed.laravelUrl ?? this.laravelUrl;
|
|
||||||
this.workspaceRoot = parsed.workspaceRoot ?? this.workspaceRoot;
|
|
||||||
this.theme = parsed.theme ?? this.theme;
|
|
||||||
} catch {
|
|
||||||
// Ignore parse errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
save(): void {
|
|
||||||
localStorage.setItem('ide-settings', JSON.stringify({
|
|
||||||
laravelUrl: this.laravelUrl,
|
|
||||||
workspaceRoot: this.workspaceRoot,
|
|
||||||
theme: this.theme,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (this.theme === 'light') {
|
|
||||||
document.documentElement.setAttribute('data-theme', 'light');
|
|
||||||
} else {
|
|
||||||
document.documentElement.removeAttribute('data-theme');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
// Type-safe wrapper for Wails v3 Go service bindings.
|
|
||||||
// At runtime, `window.go.main.{ServiceName}.{Method}()` returns a Promise.
|
|
||||||
|
|
||||||
interface WailsGo {
|
|
||||||
main: {
|
|
||||||
IDEService: {
|
|
||||||
GetConnectionStatus(): Promise<ConnectionStatus>;
|
|
||||||
GetDashboard(): Promise<DashboardData>;
|
|
||||||
ShowWindow(name: string): Promise<void>;
|
|
||||||
};
|
|
||||||
ChatService: {
|
|
||||||
SendMessage(sessionId: string, message: string): Promise<boolean>;
|
|
||||||
GetHistory(sessionId: string): Promise<ChatMessage[]>;
|
|
||||||
ListSessions(): Promise<Session[]>;
|
|
||||||
CreateSession(name: string): Promise<Session>;
|
|
||||||
GetPlanStatus(sessionId: string): Promise<PlanStatus>;
|
|
||||||
};
|
|
||||||
BuildService: {
|
|
||||||
GetBuilds(repo: string): Promise<Build[]>;
|
|
||||||
GetBuildLogs(buildId: string): Promise<string[]>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConnectionStatus {
|
|
||||||
bridgeConnected: boolean;
|
|
||||||
laravelUrl: string;
|
|
||||||
wsClients: number;
|
|
||||||
wsChannels: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DashboardData {
|
|
||||||
connection: ConnectionStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatMessage {
|
|
||||||
role: string;
|
|
||||||
content: string;
|
|
||||||
timestamp: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Session {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
status: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlanStatus {
|
|
||||||
sessionId: string;
|
|
||||||
status: string;
|
|
||||||
steps: PlanStep[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlanStep {
|
|
||||||
name: string;
|
|
||||||
status: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Build {
|
|
||||||
id: string;
|
|
||||||
repo: string;
|
|
||||||
branch: string;
|
|
||||||
status: string;
|
|
||||||
duration?: string;
|
|
||||||
startedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
go: WailsGo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
|
||||||
export class WailsService {
|
|
||||||
private get ide() { return window.go?.main?.IDEService; }
|
|
||||||
private get chat() { return window.go?.main?.ChatService; }
|
|
||||||
private get build() { return window.go?.main?.BuildService; }
|
|
||||||
|
|
||||||
// IDE
|
|
||||||
getConnectionStatus(): Promise<ConnectionStatus> {
|
|
||||||
return this.ide?.GetConnectionStatus() ?? Promise.resolve({
|
|
||||||
bridgeConnected: false, laravelUrl: '', wsClients: 0, wsChannels: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getDashboard(): Promise<DashboardData> {
|
|
||||||
return this.ide?.GetDashboard() ?? Promise.resolve({
|
|
||||||
connection: { bridgeConnected: false, laravelUrl: '', wsClients: 0, wsChannels: 0 }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showWindow(name: string): Promise<void> {
|
|
||||||
return this.ide?.ShowWindow(name) ?? Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chat
|
|
||||||
sendMessage(sessionId: string, message: string): Promise<boolean> {
|
|
||||||
return this.chat?.SendMessage(sessionId, message) ?? Promise.resolve(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
getHistory(sessionId: string): Promise<ChatMessage[]> {
|
|
||||||
return this.chat?.GetHistory(sessionId) ?? Promise.resolve([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
listSessions(): Promise<Session[]> {
|
|
||||||
return this.chat?.ListSessions() ?? Promise.resolve([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
createSession(name: string): Promise<Session> {
|
|
||||||
return this.chat?.CreateSession(name) ?? Promise.resolve({
|
|
||||||
id: '', name, status: 'offline', createdAt: ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlanStatus(sessionId: string): Promise<PlanStatus> {
|
|
||||||
return this.chat?.GetPlanStatus(sessionId) ?? Promise.resolve({
|
|
||||||
sessionId, status: 'offline', steps: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build
|
|
||||||
getBuilds(repo: string = ''): Promise<Build[]> {
|
|
||||||
return this.build?.GetBuilds(repo) ?? Promise.resolve([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
getBuildLogs(buildId: string): Promise<string[]> {
|
|
||||||
return this.build?.GetBuildLogs(buildId) ?? Promise.resolve([]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
import { Injectable, OnDestroy } from '@angular/core';
|
|
||||||
import { Subject, Observable } from 'rxjs';
|
|
||||||
import { filter } from 'rxjs/operators';
|
|
||||||
|
|
||||||
export interface WSMessage {
|
|
||||||
type: string;
|
|
||||||
channel?: string;
|
|
||||||
processId?: string;
|
|
||||||
data?: unknown;
|
|
||||||
timestamp: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
|
||||||
export class WebSocketService implements OnDestroy {
|
|
||||||
private ws: WebSocket | null = null;
|
|
||||||
private messages$ = new Subject<WSMessage>();
|
|
||||||
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
private url = 'ws://127.0.0.1:9877/ws';
|
|
||||||
private connected = false;
|
|
||||||
|
|
||||||
connect(url?: string): void {
|
|
||||||
if (url) this.url = url;
|
|
||||||
this.doConnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
private doConnect(): void {
|
|
||||||
if (this.ws) {
|
|
||||||
this.ws.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ws = new WebSocket(this.url);
|
|
||||||
|
|
||||||
this.ws.onopen = () => {
|
|
||||||
this.connected = true;
|
|
||||||
console.log('[WS] Connected');
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onmessage = (event: MessageEvent) => {
|
|
||||||
try {
|
|
||||||
const msg: WSMessage = JSON.parse(event.data);
|
|
||||||
this.messages$.next(msg);
|
|
||||||
} catch {
|
|
||||||
console.warn('[WS] Failed to parse message');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onclose = () => {
|
|
||||||
this.connected = false;
|
|
||||||
console.log('[WS] Disconnected, reconnecting in 3s...');
|
|
||||||
this.reconnectTimer = setTimeout(() => this.doConnect(), 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onerror = () => {
|
|
||||||
this.ws?.close();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(channel: string): Observable<WSMessage> {
|
|
||||||
// Send subscribe command to hub
|
|
||||||
this.send({ type: 'subscribe', data: channel, timestamp: new Date().toISOString() });
|
|
||||||
return this.messages$.pipe(
|
|
||||||
filter(msg => msg.channel === channel)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribe(channel: string): void {
|
|
||||||
this.send({ type: 'unsubscribe', data: channel, timestamp: new Date().toISOString() });
|
|
||||||
}
|
|
||||||
|
|
||||||
send(msg: WSMessage): void {
|
|
||||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
||||||
this.ws.send(JSON.stringify(msg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get isConnected(): boolean {
|
|
||||||
return this.connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
get allMessages$(): Observable<WSMessage> {
|
|
||||||
return this.messages$.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
||||||
this.ws?.close();
|
|
||||||
this.messages$.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { WailsService, ConnectionStatus } from '@shared/wails.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-tray',
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule],
|
|
||||||
template: `
|
|
||||||
<div class="tray">
|
|
||||||
<div class="tray__header">
|
|
||||||
<h3>Core IDE</h3>
|
|
||||||
<span class="badge" [class]="status.bridgeConnected ? 'badge--success' : 'badge--danger'">
|
|
||||||
{{ status.bridgeConnected ? 'Online' : 'Offline' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tray__stats">
|
|
||||||
<div class="stat">
|
|
||||||
<span class="stat__value">{{ status.wsClients }}</span>
|
|
||||||
<span class="stat__label">WS Clients</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat">
|
|
||||||
<span class="stat__value">{{ status.wsChannels }}</span>
|
|
||||||
<span class="stat__label">Channels</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tray__actions">
|
|
||||||
<button class="btn btn--primary" (click)="openMain()">Open IDE</button>
|
|
||||||
<button class="btn btn--secondary" (click)="openSettings()">Settings</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tray__footer text-muted">
|
|
||||||
Laravel bridge: {{ status.bridgeConnected ? 'connected' : 'disconnected' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
styles: [`
|
|
||||||
.tray {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tray__header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tray__stats {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat__value {
|
|
||||||
display: block;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--accent-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat__label {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tray__actions {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tray__actions .btn {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tray__footer {
|
|
||||||
margin-top: auto;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
`]
|
|
||||||
})
|
|
||||||
export class TrayComponent implements OnInit {
|
|
||||||
status: ConnectionStatus = {
|
|
||||||
bridgeConnected: false,
|
|
||||||
laravelUrl: '',
|
|
||||||
wsClients: 0,
|
|
||||||
wsChannels: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
||||||
|
|
||||||
constructor(private wails: WailsService) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.refresh();
|
|
||||||
this.pollTimer = setInterval(() => this.refresh(), 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
async refresh(): Promise<void> {
|
|
||||||
this.status = await this.wails.getConnectionStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
openMain(): void {
|
|
||||||
this.wails.showWindow('main');
|
|
||||||
}
|
|
||||||
|
|
||||||
openSettings(): void {
|
|
||||||
this.wails.showWindow('settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Core IDE</title>
|
|
||||||
<base href="/">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<app-root></app-root>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import { bootstrapApplication } from '@angular/platform-browser';
|
|
||||||
import { appConfig } from './app/app.config';
|
|
||||||
import { AppComponent } from './app/app.component';
|
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig)
|
|
||||||
.catch((err) => console.error(err));
|
|
||||||
|
|
@ -1,247 +0,0 @@
|
||||||
// Core IDE Global Styles
|
|
||||||
|
|
||||||
:root {
|
|
||||||
// Dark theme (default) — IDE accent: teal/cyan
|
|
||||||
--bg-primary: #161b22;
|
|
||||||
--bg-secondary: #0d1117;
|
|
||||||
--bg-tertiary: #21262d;
|
|
||||||
--bg-sidebar: #131820;
|
|
||||||
--text-primary: #c9d1d9;
|
|
||||||
--text-secondary: #8b949e;
|
|
||||||
--text-muted: #6e7681;
|
|
||||||
--border-color: #30363d;
|
|
||||||
--accent-primary: #39d0d8;
|
|
||||||
--accent-secondary: #58a6ff;
|
|
||||||
--accent-success: #3fb950;
|
|
||||||
--accent-warning: #d29922;
|
|
||||||
--accent-danger: #f85149;
|
|
||||||
|
|
||||||
// Spacing
|
|
||||||
--spacing-xs: 4px;
|
|
||||||
--spacing-sm: 8px;
|
|
||||||
--spacing-md: 16px;
|
|
||||||
--spacing-lg: 24px;
|
|
||||||
--spacing-xl: 32px;
|
|
||||||
|
|
||||||
// Border radius
|
|
||||||
--radius-sm: 4px;
|
|
||||||
--radius-md: 6px;
|
|
||||||
--radius-lg: 12px;
|
|
||||||
|
|
||||||
// Font
|
|
||||||
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
|
||||||
--font-mono: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
|
||||||
|
|
||||||
// IDE-specific
|
|
||||||
--sidebar-width: 240px;
|
|
||||||
--chat-input-height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: var(--font-family);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: var(--text-primary);
|
|
||||||
background-color: var(--bg-primary);
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typography
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.25;
|
|
||||||
margin-bottom: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 { font-size: 24px; }
|
|
||||||
h2 { font-size: 20px; }
|
|
||||||
h3 { font-size: 16px; }
|
|
||||||
h4 { font-size: 14px; }
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--accent-secondary);
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
code, pre {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
padding: 2px 6px;
|
|
||||||
background-color: var(--bg-tertiary);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
background-color: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scrollbar styling
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--text-muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
.btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: var(--spacing-xs);
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--primary {
|
|
||||||
background-color: var(--accent-primary);
|
|
||||||
color: #0d1117;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--secondary {
|
|
||||||
background-color: var(--bg-tertiary);
|
|
||||||
border-color: var(--border-color);
|
|
||||||
color: var(--text-primary);
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background-color: var(--bg-secondary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--danger {
|
|
||||||
background-color: var(--accent-danger);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--ghost {
|
|
||||||
background: transparent;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
color: var(--text-primary);
|
|
||||||
background-color: var(--bg-tertiary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forms
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: var(--spacing-xs);
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input,
|
|
||||||
.form-select,
|
|
||||||
.form-textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
color: var(--text-primary);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--accent-primary);
|
|
||||||
box-shadow: 0 0 0 3px rgba(57, 208, 216, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Badges
|
|
||||||
.badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 2px 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: 999px;
|
|
||||||
|
|
||||||
&--success {
|
|
||||||
background-color: rgba(63, 185, 80, 0.15);
|
|
||||||
color: var(--accent-success);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--warning {
|
|
||||||
background-color: rgba(210, 153, 34, 0.15);
|
|
||||||
color: var(--accent-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--danger {
|
|
||||||
background-color: rgba(248, 81, 73, 0.15);
|
|
||||||
color: var(--accent-danger);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--info {
|
|
||||||
background-color: rgba(57, 208, 216, 0.15);
|
|
||||||
color: var(--accent-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility classes
|
|
||||||
.text-muted { color: var(--text-muted); }
|
|
||||||
.text-success { color: var(--accent-success); }
|
|
||||||
.text-danger { color: var(--accent-danger); }
|
|
||||||
.text-warning { color: var(--accent-warning); }
|
|
||||||
.mono { font-family: var(--font-mono); }
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "./out-tsc/app",
|
|
||||||
"types": []
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"src/main.ts"
|
|
||||||
],
|
|
||||||
"include": [
|
|
||||||
"src/**/*.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
{
|
|
||||||
"compileOnSave": false,
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": "./",
|
|
||||||
"outDir": "./dist/out-tsc",
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"strict": true,
|
|
||||||
"noImplicitOverride": true,
|
|
||||||
"noPropertyAccessFromIndexSignature": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"declaration": false,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"importHelpers": true,
|
|
||||||
"target": "ES2022",
|
|
||||||
"module": "ES2022",
|
|
||||||
"lib": [
|
|
||||||
"ES2022",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"@app/*": ["src/app/*"],
|
|
||||||
"@shared/*": ["src/app/shared/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"angularCompilerOptions": {
|
|
||||||
"enableI18nLegacyMessageIdFormat": false,
|
|
||||||
"strictInjectionParameters": true,
|
|
||||||
"strictInputAccessModifiers": true,
|
|
||||||
"strictTemplates": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
module forge.lthn.ai/core/go/cmd/core-ide
|
|
||||||
|
|
||||||
go 1.25.5
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/gorilla/websocket v1.5.3
|
|
||||||
forge.lthn.ai/core/go v0.0.0
|
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.64
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
dario.cat/mergo v1.0.2 // indirect
|
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
|
||||||
github.com/adrg/xdg v0.5.3 // indirect
|
|
||||||
github.com/bep/debounce v1.2.1 // indirect
|
|
||||||
github.com/cloudflare/circl v1.6.3 // indirect
|
|
||||||
github.com/coder/websocket v1.8.14 // indirect
|
|
||||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
|
||||||
github.com/ebitengine/purego v0.9.1 // indirect
|
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
|
||||||
github.com/go-git/go-billy/v5 v5.7.0 // indirect
|
|
||||||
github.com/go-git/go-git/v5 v5.16.4 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
|
||||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
|
||||||
github.com/google/jsonschema-go v0.4.2 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
|
||||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
|
||||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
|
||||||
github.com/leaanthony/u v1.1.1 // indirect
|
|
||||||
github.com/lmittmann/tint v1.1.2 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/modelcontextprotocol/go-sdk v1.2.0 // indirect
|
|
||||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/samber/lo v1.52.0 // indirect
|
|
||||||
github.com/sergi/go-diff v1.4.0 // indirect
|
|
||||||
github.com/skeema/knownhosts v1.3.2 // indirect
|
|
||||||
github.com/wailsapp/go-webview2 v1.0.23 // indirect
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
|
||||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
|
||||||
golang.org/x/net v0.49.0 // indirect
|
|
||||||
golang.org/x/oauth2 v0.34.0 // indirect
|
|
||||||
golang.org/x/sys v0.40.0 // indirect
|
|
||||||
golang.org/x/text v0.33.0 // indirect
|
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
replace forge.lthn.ai/core/go => ../..
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
|
||||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
|
||||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
|
||||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
|
||||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
|
||||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
|
||||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
|
||||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
|
||||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
|
||||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
|
||||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
|
||||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
|
||||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
|
||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
|
||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
|
||||||
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
|
|
||||||
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
|
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
|
||||||
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
|
|
||||||
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
|
||||||
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
|
|
||||||
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
|
||||||
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
|
||||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
||||||
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
|
|
||||||
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
|
||||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
|
|
||||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
|
||||||
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
|
||||||
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
|
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
|
||||||
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
|
||||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
|
||||||
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
|
|
||||||
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
|
||||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
|
||||||
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
|
||||||
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
|
|
||||||
github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
|
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
|
||||||
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
|
||||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
|
||||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
|
||||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
|
||||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
|
||||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
|
||||||
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
|
||||||
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
|
|
||||||
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.64 h1:xAhLFVfdbg7XdZQ5mMQmBv2BglWu8hMqe50Z+3UJvBs=
|
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.64/go.mod h1:zvgNL/mlFcX8aRGu6KOz9AHrMmTBD+4hJRQIONqF/Yw=
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
|
||||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
|
||||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
|
||||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
|
||||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
|
||||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
|
||||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
|
||||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
|
||||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 198 B |
|
|
@ -1,25 +0,0 @@
|
||||||
// Package icons provides embedded icon assets for the Core IDE application.
|
|
||||||
package icons
|
|
||||||
|
|
||||||
import _ "embed"
|
|
||||||
|
|
||||||
// TrayTemplate is the template icon for macOS systray (22x22 PNG, black on transparent).
|
|
||||||
// Template icons automatically adapt to light/dark mode on macOS.
|
|
||||||
//
|
|
||||||
//go:embed tray-template.png
|
|
||||||
var TrayTemplate []byte
|
|
||||||
|
|
||||||
// TrayLight is the light mode icon for Windows/Linux systray.
|
|
||||||
//
|
|
||||||
//go:embed tray-light.png
|
|
||||||
var TrayLight []byte
|
|
||||||
|
|
||||||
// TrayDark is the dark mode icon for Windows/Linux systray.
|
|
||||||
//
|
|
||||||
//go:embed tray-dark.png
|
|
||||||
var TrayDark []byte
|
|
||||||
|
|
||||||
// AppIcon is the main application icon.
|
|
||||||
//
|
|
||||||
//go:embed appicon.png
|
|
||||||
var AppIcon []byte
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 185 B |
Binary file not shown.
|
Before Width: | Height: | Size: 196 B |
Binary file not shown.
|
Before Width: | Height: | Size: 171 B |
|
|
@ -1,83 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"forge.lthn.ai/core/go/pkg/mcp/ide"
|
|
||||||
"forge.lthn.ai/core/go/pkg/ws"
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IDEService provides core IDE bindings for the frontend.
|
|
||||||
type IDEService struct {
|
|
||||||
app *application.App
|
|
||||||
ideSub *ide.Subsystem
|
|
||||||
hub *ws.Hub
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIDEService creates a new IDEService.
|
|
||||||
func NewIDEService(ideSub *ide.Subsystem, hub *ws.Hub) *IDEService {
|
|
||||||
return &IDEService{ideSub: ideSub, hub: hub}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceName returns the service name for Wails.
|
|
||||||
func (s *IDEService) ServiceName() string { return "IDEService" }
|
|
||||||
|
|
||||||
// ServiceStartup is called when the Wails application starts.
|
|
||||||
func (s *IDEService) ServiceStartup(_ context.Context, _ application.ServiceOptions) error {
|
|
||||||
log.Println("IDEService started")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceShutdown is called when the Wails application shuts down.
|
|
||||||
func (s *IDEService) ServiceShutdown() error {
|
|
||||||
log.Println("IDEService shutdown")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectionStatus represents the IDE bridge connection state.
|
|
||||||
type ConnectionStatus struct {
|
|
||||||
BridgeConnected bool `json:"bridgeConnected"`
|
|
||||||
LaravelURL string `json:"laravelUrl"`
|
|
||||||
WSClients int `json:"wsClients"`
|
|
||||||
WSChannels int `json:"wsChannels"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConnectionStatus returns the current bridge and WebSocket status.
|
|
||||||
func (s *IDEService) GetConnectionStatus() ConnectionStatus {
|
|
||||||
connected := false
|
|
||||||
if s.ideSub.Bridge() != nil {
|
|
||||||
connected = s.ideSub.Bridge().Connected()
|
|
||||||
}
|
|
||||||
|
|
||||||
stats := s.hub.Stats()
|
|
||||||
return ConnectionStatus{
|
|
||||||
BridgeConnected: connected,
|
|
||||||
WSClients: stats.Clients,
|
|
||||||
WSChannels: stats.Channels,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DashboardData aggregates data for the dashboard view.
|
|
||||||
type DashboardData struct {
|
|
||||||
Connection ConnectionStatus `json:"connection"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDashboard returns aggregated dashboard data.
|
|
||||||
func (s *IDEService) GetDashboard() DashboardData {
|
|
||||||
return DashboardData{
|
|
||||||
Connection: s.GetConnectionStatus(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowWindow shows a named window.
|
|
||||||
func (s *IDEService) ShowWindow(name string) {
|
|
||||||
if s.app == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if w, ok := s.app.Window.Get(name); ok {
|
|
||||||
w.Show()
|
|
||||||
w.Focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
// Package main provides the Core IDE desktop application.
|
|
||||||
// Core IDE connects to the Laravel core-agentic backend via MCP bridge,
|
|
||||||
// providing a chat interface for AI agent sessions, build monitoring,
|
|
||||||
// and a system dashboard.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"embed"
|
|
||||||
"io/fs"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/ws"
|
|
||||||
"forge.lthn.ai/core/go/cmd/core-ide/icons"
|
|
||||||
"forge.lthn.ai/core/go/pkg/mcp/ide"
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed all:frontend/dist/core-ide/browser
|
|
||||||
var assets embed.FS
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
staticAssets, err := fs.Sub(assets, "frontend/dist/core-ide/browser")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create shared WebSocket hub for real-time streaming
|
|
||||||
hub := ws.NewHub()
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
go hub.Run(ctx)
|
|
||||||
|
|
||||||
// Create IDE subsystem (bridge to Laravel core-agentic)
|
|
||||||
ideSub := ide.New(hub)
|
|
||||||
ideSub.StartBridge(ctx)
|
|
||||||
|
|
||||||
// Create Wails services
|
|
||||||
ideService := NewIDEService(ideSub, hub)
|
|
||||||
chatService := NewChatService(ideSub)
|
|
||||||
buildService := NewBuildService(ideSub)
|
|
||||||
|
|
||||||
// Create MCP bridge (SERVER: HTTP tool server + CLIENT: WebSocket relay)
|
|
||||||
mcpBridge := NewMCPBridge(hub, 9877)
|
|
||||||
|
|
||||||
app := application.New(application.Options{
|
|
||||||
Name: "Core IDE",
|
|
||||||
Description: "Host UK Platform IDE - AI Agent Sessions, Build Monitoring & Dashboard",
|
|
||||||
Services: []application.Service{
|
|
||||||
application.NewService(ideService),
|
|
||||||
application.NewService(chatService),
|
|
||||||
application.NewService(buildService),
|
|
||||||
application.NewService(mcpBridge),
|
|
||||||
},
|
|
||||||
Assets: application.AssetOptions{
|
|
||||||
Handler: spaHandler(staticAssets),
|
|
||||||
},
|
|
||||||
Mac: application.MacOptions{
|
|
||||||
ActivationPolicy: application.ActivationPolicyAccessory,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
ideService.app = app
|
|
||||||
|
|
||||||
setupSystemTray(app, ideService)
|
|
||||||
|
|
||||||
log.Println("Starting Core IDE...")
|
|
||||||
log.Println(" - System tray active")
|
|
||||||
log.Println(" - MCP bridge (SERVER) on :9877")
|
|
||||||
log.Println(" - Claude bridge (CLIENT) → MCP core on :9876")
|
|
||||||
|
|
||||||
if err := app.Run(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupSystemTray configures the system tray icon, menu, and windows.
|
|
||||||
func setupSystemTray(app *application.App, ideService *IDEService) {
|
|
||||||
systray := app.SystemTray.New()
|
|
||||||
systray.SetTooltip("Core IDE")
|
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
systray.SetTemplateIcon(icons.TrayTemplate)
|
|
||||||
} else {
|
|
||||||
systray.SetDarkModeIcon(icons.TrayDark)
|
|
||||||
systray.SetIcon(icons.TrayLight)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tray panel window
|
|
||||||
trayWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|
||||||
Name: "tray-panel",
|
|
||||||
Title: "Core IDE",
|
|
||||||
Width: 400,
|
|
||||||
Height: 500,
|
|
||||||
URL: "/tray",
|
|
||||||
Hidden: true,
|
|
||||||
Frameless: true,
|
|
||||||
BackgroundColour: application.NewRGB(22, 27, 34),
|
|
||||||
})
|
|
||||||
systray.AttachWindow(trayWindow).WindowOffset(5)
|
|
||||||
|
|
||||||
// Main IDE window
|
|
||||||
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|
||||||
Name: "main",
|
|
||||||
Title: "Core IDE",
|
|
||||||
Width: 1400,
|
|
||||||
Height: 900,
|
|
||||||
URL: "/main",
|
|
||||||
Hidden: true,
|
|
||||||
BackgroundColour: application.NewRGB(22, 27, 34),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Settings window
|
|
||||||
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|
||||||
Name: "settings",
|
|
||||||
Title: "Core IDE Settings",
|
|
||||||
Width: 600,
|
|
||||||
Height: 500,
|
|
||||||
URL: "/settings",
|
|
||||||
Hidden: true,
|
|
||||||
BackgroundColour: application.NewRGB(22, 27, 34),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Tray menu
|
|
||||||
trayMenu := app.Menu.New()
|
|
||||||
|
|
||||||
statusItem := trayMenu.Add("Status: Connecting...")
|
|
||||||
statusItem.SetEnabled(false)
|
|
||||||
|
|
||||||
trayMenu.AddSeparator()
|
|
||||||
|
|
||||||
trayMenu.Add("Open IDE").OnClick(func(ctx *application.Context) {
|
|
||||||
if w, ok := app.Window.Get("main"); ok {
|
|
||||||
w.Show()
|
|
||||||
w.Focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
trayMenu.Add("Settings...").OnClick(func(ctx *application.Context) {
|
|
||||||
if w, ok := app.Window.Get("settings"); ok {
|
|
||||||
w.Show()
|
|
||||||
w.Focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
trayMenu.AddSeparator()
|
|
||||||
|
|
||||||
trayMenu.Add("Quit Core IDE").OnClick(func(ctx *application.Context) {
|
|
||||||
app.Quit()
|
|
||||||
})
|
|
||||||
|
|
||||||
systray.SetMenu(trayMenu)
|
|
||||||
}
|
|
||||||
|
|
||||||
// spaHandler wraps an fs.FS to serve static files with SPA fallback.
|
|
||||||
func spaHandler(fsys fs.FS) http.Handler {
|
|
||||||
fileServer := http.FileServer(http.FS(fsys))
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
path := strings.TrimPrefix(r.URL.Path, "/")
|
|
||||||
if path == "" {
|
|
||||||
path = "index.html"
|
|
||||||
}
|
|
||||||
if _, err := fs.Stat(fsys, path); err != nil {
|
|
||||||
r.URL.Path = "/"
|
|
||||||
}
|
|
||||||
fileServer.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,504 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"forge.lthn.ai/core/go/pkg/ws"
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MCPBridge is the SERVER bridge that exposes MCP tools via HTTP.
|
|
||||||
// AI agents call these endpoints to control windows, execute JS in webviews,
|
|
||||||
// access the clipboard, show notifications, and query the app state.
|
|
||||||
type MCPBridge struct {
|
|
||||||
app *application.App
|
|
||||||
hub *ws.Hub
|
|
||||||
claudeBridge *ClaudeBridge
|
|
||||||
port int
|
|
||||||
running bool
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMCPBridge creates a new MCP bridge server.
|
|
||||||
func NewMCPBridge(hub *ws.Hub, port int) *MCPBridge {
|
|
||||||
cb := NewClaudeBridge("ws://localhost:9876/ws")
|
|
||||||
return &MCPBridge{
|
|
||||||
hub: hub,
|
|
||||||
claudeBridge: cb,
|
|
||||||
port: port,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceName returns the Wails service name.
|
|
||||||
func (b *MCPBridge) ServiceName() string { return "MCPBridge" }
|
|
||||||
|
|
||||||
// ServiceStartup is called by Wails when the app starts.
|
|
||||||
func (b *MCPBridge) ServiceStartup(_ context.Context, _ application.ServiceOptions) error {
|
|
||||||
b.app = application.Get()
|
|
||||||
go b.startHTTPServer()
|
|
||||||
log.Printf("MCP Bridge started on port %d", b.port)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceShutdown is called when the app shuts down.
|
|
||||||
func (b *MCPBridge) ServiceShutdown() error {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
b.running = false
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// startHTTPServer starts the HTTP server for MCP tools and WebSocket.
|
|
||||||
func (b *MCPBridge) startHTTPServer() {
|
|
||||||
b.mu.Lock()
|
|
||||||
b.running = true
|
|
||||||
b.mu.Unlock()
|
|
||||||
|
|
||||||
// Start the Claude bridge (CLIENT → MCP core on :9876)
|
|
||||||
b.claudeBridge.Start()
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
|
|
||||||
// WebSocket endpoint for Angular frontend
|
|
||||||
mux.HandleFunc("/ws", b.hub.HandleWebSocket)
|
|
||||||
|
|
||||||
// Claude bridge WebSocket relay (GUI clients ↔ MCP core)
|
|
||||||
mux.HandleFunc("/claude", b.claudeBridge.HandleWebSocket)
|
|
||||||
|
|
||||||
// MCP server endpoints
|
|
||||||
mux.HandleFunc("/mcp", b.handleMCPInfo)
|
|
||||||
mux.HandleFunc("/mcp/tools", b.handleMCPTools)
|
|
||||||
mux.HandleFunc("/mcp/call", b.handleMCPCall)
|
|
||||||
|
|
||||||
// Health check
|
|
||||||
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
json.NewEncoder(w).Encode(map[string]any{
|
|
||||||
"status": "ok",
|
|
||||||
"mcp": true,
|
|
||||||
"claudeBridge": b.claudeBridge.Connected(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("127.0.0.1:%d", b.port)
|
|
||||||
log.Printf("MCP HTTP server listening on %s", addr)
|
|
||||||
|
|
||||||
if err := http.ListenAndServe(addr, mux); err != nil {
|
|
||||||
log.Printf("MCP HTTP server error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMCPInfo returns MCP server information.
|
|
||||||
func (b *MCPBridge) handleMCPInfo(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]any{
|
|
||||||
"name": "core-ide",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"capabilities": map[string]any{
|
|
||||||
"webview": true,
|
|
||||||
"windowControl": true,
|
|
||||||
"clipboard": true,
|
|
||||||
"notifications": true,
|
|
||||||
"websocket": fmt.Sprintf("ws://localhost:%d/ws", b.port),
|
|
||||||
"claude": fmt.Sprintf("ws://localhost:%d/claude", b.port),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMCPTools returns the list of available tools.
|
|
||||||
func (b *MCPBridge) handleMCPTools(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
|
|
||||||
tools := []map[string]string{
|
|
||||||
// Window management
|
|
||||||
{"name": "window_list", "description": "List all windows with positions and sizes"},
|
|
||||||
{"name": "window_get", "description": "Get info about a specific window"},
|
|
||||||
{"name": "window_position", "description": "Move a window to specific coordinates"},
|
|
||||||
{"name": "window_size", "description": "Resize a window"},
|
|
||||||
{"name": "window_bounds", "description": "Set position and size in one call"},
|
|
||||||
{"name": "window_maximize", "description": "Maximize a window"},
|
|
||||||
{"name": "window_minimize", "description": "Minimize a window"},
|
|
||||||
{"name": "window_restore", "description": "Restore from maximized/minimized"},
|
|
||||||
{"name": "window_focus", "description": "Bring window to front"},
|
|
||||||
{"name": "window_visibility", "description": "Show or hide a window"},
|
|
||||||
{"name": "window_title", "description": "Change window title"},
|
|
||||||
{"name": "window_title_get", "description": "Get current window title"},
|
|
||||||
{"name": "window_fullscreen", "description": "Toggle fullscreen mode"},
|
|
||||||
{"name": "window_always_on_top", "description": "Pin window above others"},
|
|
||||||
{"name": "window_create", "description": "Create a new window at specific position"},
|
|
||||||
{"name": "window_close", "description": "Close a window by name"},
|
|
||||||
{"name": "window_background_colour", "description": "Set window background colour with alpha"},
|
|
||||||
// Webview interaction
|
|
||||||
{"name": "webview_eval", "description": "Execute JavaScript in a window's webview"},
|
|
||||||
{"name": "webview_navigate", "description": "Navigate window to a URL"},
|
|
||||||
{"name": "webview_list", "description": "List windows with webview info"},
|
|
||||||
// System integration
|
|
||||||
{"name": "clipboard_read", "description": "Read text from system clipboard"},
|
|
||||||
{"name": "clipboard_write", "description": "Write text to system clipboard"},
|
|
||||||
// System tray
|
|
||||||
{"name": "tray_set_tooltip", "description": "Set system tray tooltip"},
|
|
||||||
{"name": "tray_set_label", "description": "Set system tray label"},
|
|
||||||
}
|
|
||||||
json.NewEncoder(w).Encode(map[string]any{"tools": tools})
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMCPCall handles tool calls via HTTP POST.
|
|
||||||
func (b *MCPBridge) handleMCPCall(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
|
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
||||||
|
|
||||||
if r.Method == "OPTIONS" {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Method != "POST" {
|
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var req struct {
|
|
||||||
Tool string `json:"tool"`
|
|
||||||
Params map[string]any `json:"params"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var result map[string]any
|
|
||||||
if len(req.Tool) > 8 && req.Tool[:8] == "webview_" {
|
|
||||||
result = b.executeWebviewTool(req.Tool, req.Params)
|
|
||||||
} else {
|
|
||||||
result = b.executeWindowTool(req.Tool, req.Params)
|
|
||||||
}
|
|
||||||
json.NewEncoder(w).Encode(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeWindowTool handles window, clipboard, tray, and notification tools.
|
|
||||||
func (b *MCPBridge) executeWindowTool(tool string, params map[string]any) map[string]any {
|
|
||||||
if b.app == nil {
|
|
||||||
return map[string]any{"error": "app not available"}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tool {
|
|
||||||
case "window_list":
|
|
||||||
return b.windowList()
|
|
||||||
|
|
||||||
case "window_get":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
return b.windowGet(name)
|
|
||||||
|
|
||||||
case "window_position":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
x := intParam(params, "x")
|
|
||||||
y := intParam(params, "y")
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
w.SetPosition(x, y)
|
|
||||||
return map[string]any{"success": true, "name": name, "x": x, "y": y}
|
|
||||||
|
|
||||||
case "window_size":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
width := intParam(params, "width")
|
|
||||||
height := intParam(params, "height")
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
w.SetSize(width, height)
|
|
||||||
return map[string]any{"success": true, "name": name, "width": width, "height": height}
|
|
||||||
|
|
||||||
case "window_bounds":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
x := intParam(params, "x")
|
|
||||||
y := intParam(params, "y")
|
|
||||||
width := intParam(params, "width")
|
|
||||||
height := intParam(params, "height")
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
w.SetPosition(x, y)
|
|
||||||
w.SetSize(width, height)
|
|
||||||
return map[string]any{"success": true, "name": name, "x": x, "y": y, "width": width, "height": height}
|
|
||||||
|
|
||||||
case "window_maximize":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
w.Maximise()
|
|
||||||
return map[string]any{"success": true, "action": "maximize"}
|
|
||||||
|
|
||||||
case "window_minimize":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
w.Minimise()
|
|
||||||
return map[string]any{"success": true, "action": "minimize"}
|
|
||||||
|
|
||||||
case "window_restore":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
w.Restore()
|
|
||||||
return map[string]any{"success": true, "action": "restore"}
|
|
||||||
|
|
||||||
case "window_focus":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
w.Show()
|
|
||||||
w.Focus()
|
|
||||||
return map[string]any{"success": true, "action": "focus"}
|
|
||||||
|
|
||||||
case "window_visibility":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
visible, _ := params["visible"].(bool)
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
if visible {
|
|
||||||
w.Show()
|
|
||||||
} else {
|
|
||||||
w.Hide()
|
|
||||||
}
|
|
||||||
return map[string]any{"success": true, "visible": visible}
|
|
||||||
|
|
||||||
case "window_title":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
title := strParam(params, "title")
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
w.SetTitle(title)
|
|
||||||
return map[string]any{"success": true, "title": title}
|
|
||||||
|
|
||||||
case "window_title_get":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
_, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
// Wails v3 Window interface has SetTitle but no Title getter;
|
|
||||||
// return the window name as a fallback identifier.
|
|
||||||
return map[string]any{"name": name}
|
|
||||||
|
|
||||||
case "window_fullscreen":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
fullscreen, _ := params["fullscreen"].(bool)
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
if fullscreen {
|
|
||||||
w.Fullscreen()
|
|
||||||
} else {
|
|
||||||
w.UnFullscreen()
|
|
||||||
}
|
|
||||||
return map[string]any{"success": true, "fullscreen": fullscreen}
|
|
||||||
|
|
||||||
case "window_always_on_top":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
onTop, _ := params["onTop"].(bool)
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
w.SetAlwaysOnTop(onTop)
|
|
||||||
return map[string]any{"success": true, "alwaysOnTop": onTop}
|
|
||||||
|
|
||||||
case "window_create":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
title := strParam(params, "title")
|
|
||||||
url := strParam(params, "url")
|
|
||||||
x := intParam(params, "x")
|
|
||||||
y := intParam(params, "y")
|
|
||||||
width := intParam(params, "width")
|
|
||||||
height := intParam(params, "height")
|
|
||||||
if width == 0 {
|
|
||||||
width = 800
|
|
||||||
}
|
|
||||||
if height == 0 {
|
|
||||||
height = 600
|
|
||||||
}
|
|
||||||
opts := application.WebviewWindowOptions{
|
|
||||||
Name: name,
|
|
||||||
Title: title,
|
|
||||||
URL: url,
|
|
||||||
Width: width,
|
|
||||||
Height: height,
|
|
||||||
Hidden: false,
|
|
||||||
BackgroundColour: application.NewRGB(22, 27, 34),
|
|
||||||
}
|
|
||||||
w := b.app.Window.NewWithOptions(opts)
|
|
||||||
if x != 0 || y != 0 {
|
|
||||||
w.SetPosition(x, y)
|
|
||||||
}
|
|
||||||
return map[string]any{"success": true, "name": name}
|
|
||||||
|
|
||||||
case "window_close":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
w.Close()
|
|
||||||
return map[string]any{"success": true, "action": "close"}
|
|
||||||
|
|
||||||
case "window_background_colour":
|
|
||||||
name := strParam(params, "name")
|
|
||||||
r := uint8(intParam(params, "r"))
|
|
||||||
g := uint8(intParam(params, "g"))
|
|
||||||
bv := uint8(intParam(params, "b"))
|
|
||||||
a := uint8(intParam(params, "a"))
|
|
||||||
if a == 0 {
|
|
||||||
a = 255
|
|
||||||
}
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
w.SetBackgroundColour(application.NewRGBA(r, g, bv, a))
|
|
||||||
return map[string]any{"success": true}
|
|
||||||
|
|
||||||
case "clipboard_read":
|
|
||||||
text, ok := b.app.Clipboard.Text()
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "failed to read clipboard"}
|
|
||||||
}
|
|
||||||
return map[string]any{"text": text}
|
|
||||||
|
|
||||||
case "clipboard_write":
|
|
||||||
text, _ := params["text"].(string)
|
|
||||||
ok := b.app.Clipboard.SetText(text)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "failed to write clipboard"}
|
|
||||||
}
|
|
||||||
return map[string]any{"success": true}
|
|
||||||
|
|
||||||
case "tray_set_tooltip":
|
|
||||||
// System tray is managed at startup; this is informational
|
|
||||||
return map[string]any{"info": "tray tooltip can be set via system tray menu"}
|
|
||||||
|
|
||||||
case "tray_set_label":
|
|
||||||
return map[string]any{"info": "tray label can be set via system tray menu"}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return map[string]any{"error": "unknown tool", "tool": tool}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeWebviewTool handles webview/JS tools.
|
|
||||||
func (b *MCPBridge) executeWebviewTool(tool string, params map[string]any) map[string]any {
|
|
||||||
if b.app == nil {
|
|
||||||
return map[string]any{"error": "app not available"}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tool {
|
|
||||||
case "webview_eval":
|
|
||||||
windowName := strParam(params, "window")
|
|
||||||
code := strParam(params, "code")
|
|
||||||
w, ok := b.app.Window.Get(windowName)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "window": windowName}
|
|
||||||
}
|
|
||||||
w.ExecJS(code)
|
|
||||||
return map[string]any{"success": true, "window": windowName}
|
|
||||||
|
|
||||||
case "webview_navigate":
|
|
||||||
windowName := strParam(params, "window")
|
|
||||||
url := strParam(params, "url")
|
|
||||||
w, ok := b.app.Window.Get(windowName)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "window": windowName}
|
|
||||||
}
|
|
||||||
w.SetURL(url)
|
|
||||||
return map[string]any{"success": true, "url": url}
|
|
||||||
|
|
||||||
case "webview_list":
|
|
||||||
return b.windowList()
|
|
||||||
|
|
||||||
default:
|
|
||||||
return map[string]any{"error": "unknown webview tool", "tool": tool}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// windowList returns info for all known windows.
|
|
||||||
func (b *MCPBridge) windowList() map[string]any {
|
|
||||||
knownNames := []string{"tray-panel", "main", "settings"}
|
|
||||||
var windows []map[string]any
|
|
||||||
for _, name := range knownNames {
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
x, y := w.Position()
|
|
||||||
width, height := w.Size()
|
|
||||||
windows = append(windows, map[string]any{
|
|
||||||
"name": name,
|
|
||||||
"title": w.Name(),
|
|
||||||
"x": x,
|
|
||||||
"y": y,
|
|
||||||
"width": width,
|
|
||||||
"height": height,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return map[string]any{"windows": windows}
|
|
||||||
}
|
|
||||||
|
|
||||||
// windowGet returns info for a specific window.
|
|
||||||
func (b *MCPBridge) windowGet(name string) map[string]any {
|
|
||||||
w, ok := b.app.Window.Get(name)
|
|
||||||
if !ok {
|
|
||||||
return map[string]any{"error": "window not found", "name": name}
|
|
||||||
}
|
|
||||||
x, y := w.Position()
|
|
||||||
width, height := w.Size()
|
|
||||||
return map[string]any{
|
|
||||||
"window": map[string]any{
|
|
||||||
"name": name,
|
|
||||||
"title": w.Name(),
|
|
||||||
"x": x,
|
|
||||||
"y": y,
|
|
||||||
"width": width,
|
|
||||||
"height": height,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parameter helpers
|
|
||||||
func strParam(params map[string]any, key string) string {
|
|
||||||
if v, ok := params[key].(string); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func intParam(params map[string]any, key string) int {
|
|
||||||
if v, ok := params[key].(float64); ok {
|
|
||||||
return int(v)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package crypt
|
package crypt
|
||||||
|
|
||||||
import "forge.lthn.ai/core/cli/pkg/cli"
|
import "forge.lthn.ai/core/go/pkg/cli"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cli.RegisterCommands(AddCryptCommands)
|
cli.RegisterCommands(AddCryptCommands)
|
||||||
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/crypt"
|
"forge.lthn.ai/core/go/pkg/crypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Checksum command flags
|
// Checksum command flags
|
||||||
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/crypt"
|
"forge.lthn.ai/core/go/pkg/crypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encrypt command flags
|
// Encrypt command flags
|
||||||
|
|
@ -3,8 +3,8 @@ package crypt
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/crypt"
|
"forge.lthn.ai/core/go/pkg/crypt"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Keygen command flags
|
// Keygen command flags
|
||||||
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/log"
|
"forge.lthn.ai/core/go/pkg/log"
|
||||||
"forge.lthn.ai/core/cli/pkg/mcp"
|
"forge.lthn.ai/core/go/pkg/mcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/ansible"
|
"forge.lthn.ai/core/go/pkg/ansible"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package deploy
|
package deploy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/deploy/coolify"
|
"forge.lthn.ai/core/go/pkg/deploy/coolify"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package dev
|
package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addAPICommands adds the 'api' command and its subcommands to the given parent command.
|
// addAPICommands adds the 'api' command and its subcommands to the given parent command.
|
||||||
|
|
@ -14,12 +14,12 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
core "forge.lthn.ai/core/cli/pkg/framework/core"
|
core "forge.lthn.ai/core/go/pkg/framework/core"
|
||||||
"forge.lthn.ai/core/cli/pkg/git"
|
"forge.lthn.ai/core/go/pkg/git"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"forge.lthn.ai/core/cli/pkg/io"
|
"forge.lthn.ai/core/go/pkg/io"
|
||||||
"forge.lthn.ai/core/cli/pkg/repos"
|
"forge.lthn.ai/core/go/pkg/repos"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Apply command flags
|
// Apply command flags
|
||||||
|
|
@ -3,9 +3,9 @@ package dev
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/agentic"
|
"forge.lthn.ai/core/go/pkg/agentic"
|
||||||
"forge.lthn.ai/core/cli/pkg/framework"
|
"forge.lthn.ai/core/go/pkg/framework"
|
||||||
"forge.lthn.ai/core/cli/pkg/git"
|
"forge.lthn.ai/core/go/pkg/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WorkBundle contains the Core instance for dev work operations.
|
// WorkBundle contains the Core instance for dev work operations.
|
||||||
|
|
@ -8,10 +8,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"forge.lthn.ai/core/cli/pkg/io"
|
"forge.lthn.ai/core/go/pkg/io"
|
||||||
"forge.lthn.ai/core/cli/pkg/repos"
|
"forge.lthn.ai/core/go/pkg/repos"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CI-specific styles (aliases to shared)
|
// CI-specific styles (aliases to shared)
|
||||||
|
|
@ -5,10 +5,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/git"
|
"forge.lthn.ai/core/go/pkg/git"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
coreio "forge.lthn.ai/core/cli/pkg/io"
|
coreio "forge.lthn.ai/core/go/pkg/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commit command flags
|
// Commit command flags
|
||||||
|
|
@ -33,8 +33,8 @@
|
||||||
package dev
|
package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -14,12 +14,12 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/git"
|
"forge.lthn.ai/core/go/pkg/git"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
coreio "forge.lthn.ai/core/cli/pkg/io"
|
coreio "forge.lthn.ai/core/go/pkg/io"
|
||||||
"forge.lthn.ai/core/cli/pkg/log"
|
"forge.lthn.ai/core/go/pkg/log"
|
||||||
"forge.lthn.ai/core/cli/pkg/repos"
|
"forge.lthn.ai/core/go/pkg/repos"
|
||||||
)
|
)
|
||||||
|
|
||||||
// File sync command flags
|
// File sync command flags
|
||||||
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/git"
|
"forge.lthn.ai/core/go/pkg/git"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Health command flags
|
// Health command flags
|
||||||
|
|
@ -4,10 +4,10 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"forge.lthn.ai/core/cli/pkg/io"
|
"forge.lthn.ai/core/go/pkg/io"
|
||||||
"forge.lthn.ai/core/cli/pkg/repos"
|
"forge.lthn.ai/core/go/pkg/repos"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Impact-specific styles (aliases to shared)
|
// Impact-specific styles (aliases to shared)
|
||||||
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Issue-specific styles (aliases to shared)
|
// Issue-specific styles (aliases to shared)
|
||||||
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/git"
|
"forge.lthn.ai/core/go/pkg/git"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Pull command flags
|
// Pull command flags
|
||||||
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/git"
|
"forge.lthn.ai/core/go/pkg/git"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Push command flags
|
// Push command flags
|
||||||
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PR-specific styles (aliases to shared)
|
// PR-specific styles (aliases to shared)
|
||||||
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli" // Added
|
"forge.lthn.ai/core/go/pkg/cli" // Added
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n" // Added
|
"forge.lthn.ai/core/go/pkg/i18n" // Added
|
||||||
coreio "forge.lthn.ai/core/cli/pkg/io"
|
coreio "forge.lthn.ai/core/go/pkg/io"
|
||||||
// Added
|
// Added
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/devops"
|
"forge.lthn.ai/core/go/pkg/devops"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"forge.lthn.ai/core/cli/pkg/io"
|
"forge.lthn.ai/core/go/pkg/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addVMCommands adds the dev environment VM commands to the dev parent command.
|
// addVMCommands adds the dev environment VM commands to the dev parent command.
|
||||||
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/agentic"
|
"forge.lthn.ai/core/go/pkg/agentic"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/git"
|
"forge.lthn.ai/core/go/pkg/git"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Work command flags
|
// Work command flags
|
||||||
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"forge.lthn.ai/core/cli/pkg/io"
|
"forge.lthn.ai/core/go/pkg/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Workflow command flags
|
// Workflow command flags
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/io"
|
"forge.lthn.ai/core/go/pkg/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFindWorkflows_Good(t *testing.T) {
|
func TestFindWorkflows_Good(t *testing.T) {
|
||||||
|
|
@ -5,11 +5,11 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/internal/cmd/workspace"
|
"forge.lthn.ai/core/cli/cmd/workspace"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"forge.lthn.ai/core/cli/pkg/io"
|
"forge.lthn.ai/core/go/pkg/io"
|
||||||
"forge.lthn.ai/core/cli/pkg/repos"
|
"forge.lthn.ai/core/go/pkg/repos"
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadRegistryWithConfig loads the registry and applies workspace configuration.
|
// loadRegistryWithConfig loads the registry and applies workspace configuration.
|
||||||
|
|
@ -5,10 +5,10 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/agentic"
|
"forge.lthn.ai/core/go/pkg/agentic"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/framework"
|
"forge.lthn.ai/core/go/pkg/framework"
|
||||||
"forge.lthn.ai/core/cli/pkg/git"
|
"forge.lthn.ai/core/go/pkg/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tasks for dev service
|
// Tasks for dev service
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
// to a central location for unified documentation builds.
|
// to a central location for unified documentation builds.
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
import "forge.lthn.ai/core/cli/pkg/cli"
|
import "forge.lthn.ai/core/go/pkg/cli"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cli.RegisterCommands(AddDocsCommands)
|
cli.RegisterCommands(AddDocsCommands)
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style and utility aliases from shared
|
// Style and utility aliases from shared
|
||||||
|
|
@ -3,8 +3,8 @@ package docs
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Flag variable for list command
|
// Flag variable for list command
|
||||||
|
|
@ -6,11 +6,11 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/internal/cmd/workspace"
|
"forge.lthn.ai/core/cli/cmd/workspace"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"forge.lthn.ai/core/cli/pkg/io"
|
"forge.lthn.ai/core/go/pkg/io"
|
||||||
"forge.lthn.ai/core/cli/pkg/repos"
|
"forge.lthn.ai/core/go/pkg/repos"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RepoDocInfo holds documentation info for a repo
|
// RepoDocInfo holds documentation info for a repo
|
||||||
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"forge.lthn.ai/core/cli/pkg/io"
|
"forge.lthn.ai/core/go/pkg/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Flag variables for sync command
|
// Flag variables for sync command
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// check represents a tool check configuration
|
// check represents a tool check configuration
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
package doctor
|
package doctor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -4,8 +4,8 @@ package doctor
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
"forge.lthn.ai/core/cli/pkg/io"
|
"forge.lthn.ai/core/go/pkg/io"
|
||||||
"forge.lthn.ai/core/cli/pkg/repos"
|
"forge.lthn.ai/core/go/pkg/repos"
|
||||||
)
|
)
|
||||||
|
|
||||||
// checkGitHubSSH checks if SSH keys exist for GitHub access
|
// checkGitHubSSH checks if SSH keys exist for GitHub access
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/i18n"
|
"forge.lthn.ai/core/go/pkg/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// printInstallInstructions prints OS-specific installation instructions
|
// printInstallInstructions prints OS-specific installation instructions
|
||||||
|
|
@ -3,8 +3,8 @@ package forge
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/go/pkg/cli"
|
||||||
fg "forge.lthn.ai/core/cli/pkg/forge"
|
fg "forge.lthn.ai/core/go/pkg/forge"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auth command flags.
|
// Auth command flags.
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue