refactor: move plans to tasks/, framework docs to core-gui
- plans/ → tasks/plans/ (planning documents) - framework/ → core-gui/docs/framework/ (GUI framework docs) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8601e5b355
commit
77a7237d71
31 changed files with 0 additions and 3529 deletions
|
|
@ -1,134 +0,0 @@
|
|||
# Architecture
|
||||
|
||||
Core follows a modular, service-based architecture designed for maintainability and testability.
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Wails Application │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Core │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Display │ │ WebView │ │ MCP │ │ Config │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Crypt │ │ I18n │ │ IO │ │Workspace │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Plugin System │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Plugin A │ │ Plugin B │ │ Plugin C │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Core Container
|
||||
|
||||
The `Core` struct is the central service container:
|
||||
|
||||
```go
|
||||
type Core struct {
|
||||
services map[string]any // Service registry
|
||||
actions []ActionHandler // IPC handlers
|
||||
Features *Features // Feature flags
|
||||
servicesLocked bool // Prevent late registration
|
||||
}
|
||||
```
|
||||
|
||||
### Service Registration
|
||||
|
||||
Services are registered using factory functions:
|
||||
|
||||
```go
|
||||
core.New(
|
||||
core.WithService(display.NewService), // Auto-discovered name
|
||||
core.WithName("custom", myFactory), // Explicit name
|
||||
)
|
||||
```
|
||||
|
||||
### Service Retrieval
|
||||
|
||||
Type-safe service retrieval:
|
||||
|
||||
```go
|
||||
// Returns error if not found
|
||||
svc, err := core.ServiceFor[*display.Service](c, "display")
|
||||
|
||||
// Panics if not found (use in init code)
|
||||
svc := core.MustServiceFor[*display.Service](c, "display")
|
||||
```
|
||||
|
||||
## Service Lifecycle
|
||||
|
||||
Services can implement lifecycle interfaces:
|
||||
|
||||
```go
|
||||
// Called when app starts
|
||||
type Startable interface {
|
||||
OnStartup(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Called when app shuts down
|
||||
type Stoppable interface {
|
||||
OnShutdown(ctx context.Context) error
|
||||
}
|
||||
```
|
||||
|
||||
## IPC / Actions
|
||||
|
||||
Services communicate via the action system:
|
||||
|
||||
```go
|
||||
// Register a handler
|
||||
c.RegisterAction(func(c *core.Core, msg core.Message) error {
|
||||
if msg.Type == "my-action" {
|
||||
// Handle message
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Send a message
|
||||
c.ACTION(core.Message{
|
||||
Type: "my-action",
|
||||
Data: map[string]any{"key": "value"},
|
||||
})
|
||||
```
|
||||
|
||||
## Frontend Bindings
|
||||
|
||||
Wails generates TypeScript bindings automatically:
|
||||
|
||||
```typescript
|
||||
// Auto-generated from Go service
|
||||
import { ShowNotification } from '@bindings/display/service';
|
||||
|
||||
await ShowNotification({
|
||||
title: "Hello",
|
||||
message: "From TypeScript!"
|
||||
});
|
||||
```
|
||||
|
||||
## Package Structure
|
||||
|
||||
```
|
||||
pkg/
|
||||
├── core/ # Core container and interfaces
|
||||
├── display/ # Window, tray, dialogs, clipboard
|
||||
├── webview/ # JS execution, DOM, screenshots
|
||||
├── mcp/ # Model Context Protocol server
|
||||
├── config/ # Configuration persistence
|
||||
├── crypt/ # Encryption and signing
|
||||
├── i18n/ # Internationalization
|
||||
├── io/ # File system helpers
|
||||
├── workspace/ # Project management
|
||||
├── plugin/ # Plugin system
|
||||
└── module/ # Module system
|
||||
```
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **Dependency Injection**: Services receive dependencies via constructor
|
||||
2. **Interface Segregation**: Small, focused interfaces
|
||||
3. **Testability**: All services are mockable
|
||||
4. **No Globals**: State contained in Core instance
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
# Config Service
|
||||
|
||||
The Config service (`pkg/config`) provides unified configuration management with automatic persistence, feature flags, and XDG-compliant directory paths.
|
||||
|
||||
## Features
|
||||
|
||||
- JSON configuration with auto-save
|
||||
- Feature flag management
|
||||
- XDG Base Directory support
|
||||
- Struct serialization helpers
|
||||
- Type-safe get/set operations
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/config"
|
||||
|
||||
// Standalone usage
|
||||
cfg, err := config.New()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// With Core framework
|
||||
c, _ := core.New(
|
||||
core.WithService(config.Register),
|
||||
)
|
||||
cfg := core.MustServiceFor[*config.Service](c, "config")
|
||||
```
|
||||
|
||||
## Get & Set Values
|
||||
|
||||
```go
|
||||
// Set a value (auto-saves)
|
||||
err := cfg.Set("language", "fr")
|
||||
|
||||
// Get a value
|
||||
var lang string
|
||||
err := cfg.Get("language", &lang)
|
||||
```
|
||||
|
||||
Available configuration keys:
|
||||
|
||||
| Key | Type | Description |
|
||||
|-----|------|-------------|
|
||||
| `language` | string | UI language code |
|
||||
| `default_route` | string | Default navigation route |
|
||||
| `configDir` | string | Config files directory |
|
||||
| `dataDir` | string | Data files directory |
|
||||
| `cacheDir` | string | Cache directory |
|
||||
| `workspaceDir` | string | Workspaces directory |
|
||||
|
||||
## Feature Flags
|
||||
|
||||
```go
|
||||
// Enable a feature
|
||||
cfg.EnableFeature("dark_mode")
|
||||
|
||||
// Check if enabled
|
||||
if cfg.IsFeatureEnabled("dark_mode") {
|
||||
// Apply dark theme
|
||||
}
|
||||
|
||||
// Disable a feature
|
||||
cfg.DisableFeature("dark_mode")
|
||||
```
|
||||
|
||||
## Struct Serialization
|
||||
|
||||
Store complex data structures in separate JSON files:
|
||||
|
||||
```go
|
||||
type UserPrefs struct {
|
||||
Theme string `json:"theme"`
|
||||
Notifications bool `json:"notifications"`
|
||||
}
|
||||
|
||||
// Save struct to config/user_prefs.json
|
||||
prefs := UserPrefs{Theme: "dark", Notifications: true}
|
||||
err := cfg.SaveStruct("user_prefs", prefs)
|
||||
|
||||
// Load struct from file
|
||||
var loaded UserPrefs
|
||||
err := cfg.LoadStruct("user_prefs", &loaded)
|
||||
```
|
||||
|
||||
## Directory Paths
|
||||
|
||||
The service automatically creates XDG-compliant directories:
|
||||
|
||||
```go
|
||||
// Access directory paths
|
||||
fmt.Println(cfg.ConfigDir) // ~/.config/lethean or ~/lethean/config
|
||||
fmt.Println(cfg.DataDir) // Data storage
|
||||
fmt.Println(cfg.CacheDir) // Cache files
|
||||
fmt.Println(cfg.WorkspaceDir) // User workspaces
|
||||
```
|
||||
|
||||
## Manual Save
|
||||
|
||||
Changes are auto-saved, but you can save explicitly:
|
||||
|
||||
```go
|
||||
err := cfg.Save()
|
||||
```
|
||||
|
||||
## Frontend Usage (TypeScript)
|
||||
|
||||
```typescript
|
||||
import { Get, Set, IsFeatureEnabled } from '@bindings/config/service';
|
||||
|
||||
// Get configuration
|
||||
const lang = await Get("language");
|
||||
|
||||
// Set configuration
|
||||
await Set("default_route", "/dashboard");
|
||||
|
||||
// Check feature flag
|
||||
if (await IsFeatureEnabled("dark_mode")) {
|
||||
applyDarkTheme();
|
||||
}
|
||||
```
|
||||
|
|
@ -1,312 +0,0 @@
|
|||
# Core API Reference
|
||||
|
||||
Complete API reference for the Core framework (`pkg/core`).
|
||||
|
||||
## Core Struct
|
||||
|
||||
The central application container.
|
||||
|
||||
### Creation
|
||||
|
||||
```go
|
||||
func New(opts ...Option) (*Core, error)
|
||||
```
|
||||
|
||||
Creates a new Core instance with the specified options.
|
||||
|
||||
### Methods
|
||||
|
||||
#### Service Access
|
||||
|
||||
```go
|
||||
func ServiceFor[T any](c *Core, name string) (T, error)
|
||||
```
|
||||
|
||||
Retrieves a service by name with type safety.
|
||||
|
||||
```go
|
||||
func MustServiceFor[T any](c *Core, name string) T
|
||||
```
|
||||
|
||||
Retrieves a service by name, panics if not found or wrong type.
|
||||
|
||||
#### Actions
|
||||
|
||||
```go
|
||||
func (c *Core) ACTION(msg Message) error
|
||||
```
|
||||
|
||||
Broadcasts a message to all registered action handlers.
|
||||
|
||||
```go
|
||||
func (c *Core) RegisterAction(handler func(*Core, Message) error)
|
||||
```
|
||||
|
||||
Registers an action handler.
|
||||
|
||||
#### Service Registration
|
||||
|
||||
```go
|
||||
func (c *Core) AddService(name string, svc any) error
|
||||
```
|
||||
|
||||
Manually adds a service to the registry.
|
||||
|
||||
#### Config Access
|
||||
|
||||
```go
|
||||
func (c *Core) Config() *config.Service
|
||||
```
|
||||
|
||||
Returns the config service if registered.
|
||||
|
||||
## Options
|
||||
|
||||
### WithService
|
||||
|
||||
```go
|
||||
func WithService(factory ServiceFactory) Option
|
||||
```
|
||||
|
||||
Registers a service using its factory function.
|
||||
|
||||
```go
|
||||
c, _ := core.New(
|
||||
core.WithService(config.Register),
|
||||
core.WithService(display.NewService),
|
||||
)
|
||||
```
|
||||
|
||||
### WithName
|
||||
|
||||
```go
|
||||
func WithName(name string, factory ServiceFactory) Option
|
||||
```
|
||||
|
||||
Registers a service with an explicit name.
|
||||
|
||||
```go
|
||||
c, _ := core.New(
|
||||
core.WithName("mydb", database.NewService),
|
||||
)
|
||||
```
|
||||
|
||||
### WithAssets
|
||||
|
||||
```go
|
||||
func WithAssets(assets embed.FS) Option
|
||||
```
|
||||
|
||||
Sets embedded assets for the application.
|
||||
|
||||
### WithServiceLock
|
||||
|
||||
```go
|
||||
func WithServiceLock() Option
|
||||
```
|
||||
|
||||
Prevents late service registration after initialization.
|
||||
|
||||
## ServiceFactory
|
||||
|
||||
```go
|
||||
type ServiceFactory func(c *Core) (any, error)
|
||||
```
|
||||
|
||||
Factory function signature for service creation.
|
||||
|
||||
## Message
|
||||
|
||||
```go
|
||||
type Message interface{}
|
||||
```
|
||||
|
||||
Messages can be any type. Common patterns:
|
||||
|
||||
```go
|
||||
// Map-based message
|
||||
c.ACTION(map[string]any{
|
||||
"action": "user.created",
|
||||
"id": "123",
|
||||
})
|
||||
|
||||
// Typed message
|
||||
type UserCreated struct {
|
||||
ID string
|
||||
Email string
|
||||
}
|
||||
c.ACTION(UserCreated{ID: "123", Email: "user@example.com"})
|
||||
```
|
||||
|
||||
## ServiceRuntime
|
||||
|
||||
Generic helper for services that need Core access.
|
||||
|
||||
```go
|
||||
type ServiceRuntime[T any] struct {
|
||||
core *Core
|
||||
options T
|
||||
}
|
||||
```
|
||||
|
||||
### Creation
|
||||
|
||||
```go
|
||||
func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T]
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
```go
|
||||
func (r *ServiceRuntime[T]) Core() *Core
|
||||
func (r *ServiceRuntime[T]) Options() T
|
||||
func (r *ServiceRuntime[T]) Config() *config.Service
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```go
|
||||
type MyOptions struct {
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type MyService struct {
|
||||
*core.ServiceRuntime[MyOptions]
|
||||
}
|
||||
|
||||
func NewMyService(c *core.Core) (any, error) {
|
||||
opts := MyOptions{Timeout: 30 * time.Second}
|
||||
return &MyService{
|
||||
ServiceRuntime: core.NewServiceRuntime(c, opts),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MyService) DoSomething() {
|
||||
timeout := s.Options().Timeout
|
||||
cfg := s.Config()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Lifecycle Interfaces
|
||||
|
||||
### Startable
|
||||
|
||||
```go
|
||||
type Startable interface {
|
||||
OnStartup(ctx context.Context) error
|
||||
}
|
||||
```
|
||||
|
||||
Implement for initialization on app start.
|
||||
|
||||
### Stoppable
|
||||
|
||||
```go
|
||||
type Stoppable interface {
|
||||
OnShutdown(ctx context.Context) error
|
||||
}
|
||||
```
|
||||
|
||||
Implement for cleanup on app shutdown.
|
||||
|
||||
### IPC Handler
|
||||
|
||||
```go
|
||||
type IPCHandler interface {
|
||||
HandleIPCEvents(c *Core, msg Message) error
|
||||
}
|
||||
```
|
||||
|
||||
Automatically registered when using `WithService`.
|
||||
|
||||
## Built-in Actions
|
||||
|
||||
### ActionServiceStartup
|
||||
|
||||
```go
|
||||
type ActionServiceStartup struct{}
|
||||
```
|
||||
|
||||
Sent to all services when application starts.
|
||||
|
||||
### ActionServiceShutdown
|
||||
|
||||
```go
|
||||
type ActionServiceShutdown struct{}
|
||||
```
|
||||
|
||||
Sent to all services when application shuts down.
|
||||
|
||||
## Error Helpers
|
||||
|
||||
```go
|
||||
func E(service, operation string, err error) error
|
||||
```
|
||||
|
||||
Creates a contextual error with service and operation info.
|
||||
|
||||
```go
|
||||
if err != nil {
|
||||
return core.E("myservice", "Connect", err)
|
||||
}
|
||||
// Error: myservice.Connect: connection refused
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Snider/Core/pkg/core"
|
||||
"github.com/Snider/Core/pkg/config"
|
||||
)
|
||||
|
||||
type MyService struct {
|
||||
*core.ServiceRuntime[struct{}]
|
||||
data string
|
||||
}
|
||||
|
||||
func NewMyService(c *core.Core) (any, error) {
|
||||
return &MyService{
|
||||
ServiceRuntime: core.NewServiceRuntime(c, struct{}{}),
|
||||
data: "initialized",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MyService) OnStartup(ctx context.Context) error {
|
||||
// Startup logic
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MyService) OnShutdown(ctx context.Context) error {
|
||||
// Cleanup logic
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MyService) HandleIPCEvents(c *core.Core, msg core.Message) error {
|
||||
switch m := msg.(type) {
|
||||
case map[string]any:
|
||||
if m["action"] == "myservice.update" {
|
||||
s.data = m["data"].(string)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
c, err := core.New(
|
||||
core.WithService(config.Register),
|
||||
core.WithService(NewMyService),
|
||||
core.WithServiceLock(),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
svc := core.MustServiceFor[*MyService](c, "main")
|
||||
_ = svc
|
||||
}
|
||||
```
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
# Crypt Service
|
||||
|
||||
The Crypt service (`pkg/crypt`) provides cryptographic utilities including hashing, checksums, RSA encryption, and PGP operations.
|
||||
|
||||
## Features
|
||||
|
||||
- Multiple hash algorithms (SHA512, SHA256, SHA1, MD5)
|
||||
- Checksum functions (Fletcher, Luhn)
|
||||
- RSA key generation and encryption
|
||||
- PGP encryption, signing, and verification
|
||||
- Symmetric PGP encryption
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/crypt"
|
||||
|
||||
// Standalone usage
|
||||
crypto, err := crypt.New()
|
||||
|
||||
// With Core framework
|
||||
c, _ := core.New(
|
||||
core.WithService(crypt.Register),
|
||||
)
|
||||
crypto := core.MustServiceFor[*crypt.Service](c, "crypt")
|
||||
```
|
||||
|
||||
## Hashing
|
||||
|
||||
```go
|
||||
// Available algorithms: SHA512, SHA256, SHA1, MD5, LTHN
|
||||
hash := crypto.Hash(crypt.SHA256, "hello world")
|
||||
|
||||
// Check if string is valid hash algorithm
|
||||
isValid := crypto.IsHashAlgo("sha256")
|
||||
```
|
||||
|
||||
## Checksums
|
||||
|
||||
```go
|
||||
// Luhn validation (credit card numbers)
|
||||
isValid := crypto.Luhn("4532015112830366")
|
||||
|
||||
// Fletcher checksums
|
||||
f16 := crypto.Fletcher16("data")
|
||||
f32 := crypto.Fletcher32("data")
|
||||
f64 := crypto.Fletcher64("data")
|
||||
```
|
||||
|
||||
## RSA Encryption
|
||||
|
||||
```go
|
||||
// Generate key pair (2048 or 4096 bits recommended)
|
||||
publicKey, privateKey, err := crypto.GenerateRSAKeyPair(2048)
|
||||
|
||||
// Encrypt with public key
|
||||
ciphertext, err := crypto.EncryptRSA(publicKey, "secret message")
|
||||
|
||||
// Decrypt with private key
|
||||
plaintext, err := crypto.DecryptRSA(privateKey, ciphertext)
|
||||
```
|
||||
|
||||
## PGP Encryption
|
||||
|
||||
### Key Generation
|
||||
|
||||
```go
|
||||
// Generate PGP key pair
|
||||
publicKey, privateKey, err := crypto.GeneratePGPKeyPair(
|
||||
"User Name",
|
||||
"user@example.com",
|
||||
"Key comment",
|
||||
)
|
||||
```
|
||||
|
||||
### Asymmetric Encryption
|
||||
|
||||
```go
|
||||
// Encrypt for recipient
|
||||
ciphertext, err := crypto.EncryptPGPToString(recipientPublicKey, "secret data")
|
||||
|
||||
// Decrypt with private key
|
||||
plaintext, err := crypto.DecryptPGP(privateKey, ciphertext)
|
||||
```
|
||||
|
||||
### Symmetric Encryption
|
||||
|
||||
```go
|
||||
var buf bytes.Buffer
|
||||
err := crypto.SymmetricallyEncryptPGP(&buf, "data", "passphrase")
|
||||
```
|
||||
|
||||
### Signing & Verification
|
||||
|
||||
```go
|
||||
// Sign data
|
||||
signature, err := crypto.SignPGP(privateKey, "data to sign")
|
||||
|
||||
// Verify signature
|
||||
err := crypto.VerifyPGP(publicKey, "data to sign", signature)
|
||||
if err != nil {
|
||||
// Signature invalid
|
||||
}
|
||||
```
|
||||
|
||||
## Hash Types
|
||||
|
||||
| Constant | Algorithm |
|
||||
|----------|-----------|
|
||||
| `crypt.SHA512` | SHA-512 |
|
||||
| `crypt.SHA256` | SHA-256 |
|
||||
| `crypt.SHA1` | SHA-1 |
|
||||
| `crypt.MD5` | MD5 |
|
||||
| `crypt.LTHN` | Custom LTHN hash |
|
||||
|
||||
## Frontend Usage (TypeScript)
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Hash,
|
||||
GenerateRSAKeyPair,
|
||||
EncryptRSA,
|
||||
DecryptRSA
|
||||
} from '@bindings/crypt/service';
|
||||
|
||||
// Hash data
|
||||
const hash = await Hash("SHA256", "hello world");
|
||||
|
||||
// RSA encryption
|
||||
const { publicKey, privateKey } = await GenerateRSAKeyPair(2048);
|
||||
const encrypted = await EncryptRSA(publicKey, "secret");
|
||||
const decrypted = await DecryptRSA(privateKey, encrypted);
|
||||
```
|
||||
|
|
@ -1,352 +0,0 @@
|
|||
# Display API Reference
|
||||
|
||||
Complete API reference for the Display service (`pkg/display`).
|
||||
|
||||
## Service Creation
|
||||
|
||||
```go
|
||||
func NewService(c *core.Core) (any, error)
|
||||
```
|
||||
|
||||
## Window Management
|
||||
|
||||
### CreateWindow
|
||||
|
||||
```go
|
||||
func (s *Service) CreateWindow(opts CreateWindowOptions) (*WindowInfo, error)
|
||||
```
|
||||
|
||||
Creates a new window with the specified options.
|
||||
|
||||
```go
|
||||
type CreateWindowOptions struct {
|
||||
Name string
|
||||
Title string
|
||||
URL string
|
||||
X int
|
||||
Y int
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
```
|
||||
|
||||
### CloseWindow
|
||||
|
||||
```go
|
||||
func (s *Service) CloseWindow(name string) error
|
||||
```
|
||||
|
||||
### GetWindowInfo
|
||||
|
||||
```go
|
||||
func (s *Service) GetWindowInfo(name string) (*WindowInfo, error)
|
||||
```
|
||||
|
||||
Returns:
|
||||
|
||||
```go
|
||||
type WindowInfo struct {
|
||||
Name string
|
||||
Title string
|
||||
X int
|
||||
Y int
|
||||
Width int
|
||||
Height int
|
||||
IsVisible bool
|
||||
IsFocused bool
|
||||
IsMaximized bool
|
||||
IsMinimized bool
|
||||
}
|
||||
```
|
||||
|
||||
### ListWindowInfos
|
||||
|
||||
```go
|
||||
func (s *Service) ListWindowInfos() []*WindowInfo
|
||||
```
|
||||
|
||||
### Window Position & Size
|
||||
|
||||
```go
|
||||
func (s *Service) SetWindowPosition(name string, x, y int) error
|
||||
func (s *Service) SetWindowSize(name string, width, height int) error
|
||||
func (s *Service) SetWindowBounds(name string, x, y, width, height int) error
|
||||
```
|
||||
|
||||
### Window State
|
||||
|
||||
```go
|
||||
func (s *Service) MaximizeWindow(name string) error
|
||||
func (s *Service) MinimizeWindow(name string) error
|
||||
func (s *Service) RestoreWindow(name string) error
|
||||
func (s *Service) FocusWindow(name string) error
|
||||
func (s *Service) SetWindowFullscreen(name string, fullscreen bool) error
|
||||
func (s *Service) SetWindowAlwaysOnTop(name string, onTop bool) error
|
||||
func (s *Service) SetWindowVisibility(name string, visible bool) error
|
||||
```
|
||||
|
||||
### Window Title
|
||||
|
||||
```go
|
||||
func (s *Service) SetWindowTitle(name, title string) error
|
||||
func (s *Service) GetWindowTitle(name string) (string, error)
|
||||
```
|
||||
|
||||
### Window Background
|
||||
|
||||
```go
|
||||
func (s *Service) SetWindowBackgroundColour(name string, r, g, b, a uint8) error
|
||||
```
|
||||
|
||||
### Focus
|
||||
|
||||
```go
|
||||
func (s *Service) GetFocusedWindow() string
|
||||
```
|
||||
|
||||
## Screen Management
|
||||
|
||||
### GetScreens
|
||||
|
||||
```go
|
||||
func (s *Service) GetScreens() []*Screen
|
||||
```
|
||||
|
||||
Returns:
|
||||
|
||||
```go
|
||||
type Screen struct {
|
||||
ID string
|
||||
Name string
|
||||
X int
|
||||
Y int
|
||||
Width int
|
||||
Height int
|
||||
ScaleFactor float64
|
||||
IsPrimary bool
|
||||
}
|
||||
```
|
||||
|
||||
### GetScreen
|
||||
|
||||
```go
|
||||
func (s *Service) GetScreen(id string) (*Screen, error)
|
||||
```
|
||||
|
||||
### GetPrimaryScreen
|
||||
|
||||
```go
|
||||
func (s *Service) GetPrimaryScreen() (*Screen, error)
|
||||
```
|
||||
|
||||
### GetScreenAtPoint
|
||||
|
||||
```go
|
||||
func (s *Service) GetScreenAtPoint(x, y int) (*Screen, error)
|
||||
```
|
||||
|
||||
### GetScreenForWindow
|
||||
|
||||
```go
|
||||
func (s *Service) GetScreenForWindow(name string) (*Screen, error)
|
||||
```
|
||||
|
||||
### GetWorkAreas
|
||||
|
||||
```go
|
||||
func (s *Service) GetWorkAreas() []*WorkArea
|
||||
```
|
||||
|
||||
Returns usable screen space (excluding dock/taskbar).
|
||||
|
||||
## Layout Management
|
||||
|
||||
### SaveLayout / RestoreLayout
|
||||
|
||||
```go
|
||||
func (s *Service) SaveLayout(name string) error
|
||||
func (s *Service) RestoreLayout(name string) error
|
||||
func (s *Service) ListLayouts() []string
|
||||
func (s *Service) DeleteLayout(name string) error
|
||||
func (s *Service) GetLayout(name string) *Layout
|
||||
```
|
||||
|
||||
### TileWindows
|
||||
|
||||
```go
|
||||
func (s *Service) TileWindows(mode TileMode, windows []string) error
|
||||
```
|
||||
|
||||
Tile modes:
|
||||
|
||||
```go
|
||||
const (
|
||||
TileModeLeft TileMode = "left"
|
||||
TileModeRight TileMode = "right"
|
||||
TileModeGrid TileMode = "grid"
|
||||
TileModeQuadrants TileMode = "quadrants"
|
||||
)
|
||||
```
|
||||
|
||||
### SnapWindow
|
||||
|
||||
```go
|
||||
func (s *Service) SnapWindow(name string, position SnapPosition) error
|
||||
```
|
||||
|
||||
Snap positions:
|
||||
|
||||
```go
|
||||
const (
|
||||
SnapPositionLeft SnapPosition = "left"
|
||||
SnapPositionRight SnapPosition = "right"
|
||||
SnapPositionTop SnapPosition = "top"
|
||||
SnapPositionBottom SnapPosition = "bottom"
|
||||
SnapPositionTopLeft SnapPosition = "top-left"
|
||||
SnapPositionTopRight SnapPosition = "top-right"
|
||||
SnapPositionBottomLeft SnapPosition = "bottom-left"
|
||||
SnapPositionBottomRight SnapPosition = "bottom-right"
|
||||
)
|
||||
```
|
||||
|
||||
### StackWindows
|
||||
|
||||
```go
|
||||
func (s *Service) StackWindows(windows []string, offsetX, offsetY int) error
|
||||
```
|
||||
|
||||
### ApplyWorkflowLayout
|
||||
|
||||
```go
|
||||
func (s *Service) ApplyWorkflowLayout(workflow WorkflowType) error
|
||||
```
|
||||
|
||||
Workflow types:
|
||||
|
||||
```go
|
||||
const (
|
||||
WorkflowCoding WorkflowType = "coding"
|
||||
WorkflowDebugging WorkflowType = "debugging"
|
||||
WorkflowPresenting WorkflowType = "presenting"
|
||||
)
|
||||
```
|
||||
|
||||
## Dialogs
|
||||
|
||||
### File Dialogs
|
||||
|
||||
```go
|
||||
func (s *Service) OpenSingleFileDialog(opts OpenFileOptions) (string, error)
|
||||
func (s *Service) OpenFileDialog(opts OpenFileOptions) ([]string, error)
|
||||
func (s *Service) SaveFileDialog(opts SaveFileOptions) (string, error)
|
||||
func (s *Service) OpenDirectoryDialog(opts OpenDirectoryOptions) (string, error)
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
```go
|
||||
type OpenFileOptions struct {
|
||||
Title string
|
||||
DefaultDirectory string
|
||||
AllowMultiple bool
|
||||
Filters []FileFilter
|
||||
}
|
||||
|
||||
type SaveFileOptions struct {
|
||||
Title string
|
||||
DefaultDirectory string
|
||||
DefaultFilename string
|
||||
Filters []FileFilter
|
||||
}
|
||||
|
||||
type FileFilter struct {
|
||||
DisplayName string
|
||||
Pattern string // e.g., "*.png;*.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
### ConfirmDialog
|
||||
|
||||
```go
|
||||
func (s *Service) ConfirmDialog(title, message string) (bool, error)
|
||||
```
|
||||
|
||||
### PromptDialog
|
||||
|
||||
```go
|
||||
func (s *Service) PromptDialog(title, message string) (string, bool, error)
|
||||
```
|
||||
|
||||
## System Tray
|
||||
|
||||
```go
|
||||
func (s *Service) SetTrayIcon(icon []byte) error
|
||||
func (s *Service) SetTrayTooltip(tooltip string) error
|
||||
func (s *Service) SetTrayLabel(label string) error
|
||||
func (s *Service) SetTrayMenu(items []TrayMenuItem) error
|
||||
func (s *Service) GetTrayInfo() map[string]any
|
||||
```
|
||||
|
||||
Menu item:
|
||||
|
||||
```go
|
||||
type TrayMenuItem struct {
|
||||
Label string
|
||||
ActionID string
|
||||
IsSeparator bool
|
||||
}
|
||||
```
|
||||
|
||||
## Clipboard
|
||||
|
||||
```go
|
||||
func (s *Service) ReadClipboard() (string, error)
|
||||
func (s *Service) WriteClipboard(text string) error
|
||||
func (s *Service) HasClipboard() bool
|
||||
func (s *Service) ClearClipboard() error
|
||||
```
|
||||
|
||||
## Notifications
|
||||
|
||||
```go
|
||||
func (s *Service) ShowNotification(opts NotificationOptions) error
|
||||
func (s *Service) ShowInfoNotification(title, message string) error
|
||||
func (s *Service) ShowWarningNotification(title, message string) error
|
||||
func (s *Service) ShowErrorNotification(title, message string) error
|
||||
func (s *Service) RequestNotificationPermission() (bool, error)
|
||||
func (s *Service) CheckNotificationPermission() (bool, error)
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
```go
|
||||
type NotificationOptions struct {
|
||||
ID string
|
||||
Title string
|
||||
Message string
|
||||
Subtitle string
|
||||
}
|
||||
```
|
||||
|
||||
## Theme
|
||||
|
||||
```go
|
||||
func (s *Service) GetTheme() *Theme
|
||||
func (s *Service) GetSystemTheme() string
|
||||
```
|
||||
|
||||
Returns:
|
||||
|
||||
```go
|
||||
type Theme struct {
|
||||
IsDark bool
|
||||
}
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
```go
|
||||
func (s *Service) GetEventManager() *EventManager
|
||||
```
|
||||
|
||||
The EventManager handles WebSocket connections for real-time events.
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
---
|
||||
title: e
|
||||
---
|
||||
# Service: `e`
|
||||
|
||||
Package e provides a standardized error handling mechanism for the Core library.
|
||||
It allows for wrapping errors with contextual information, making it easier to
|
||||
trace the origin of an error and provide meaningful feedback.
|
||||
|
||||
The design of this package is influenced by the need for a simple, yet powerful
|
||||
way to handle errors that can occur in different layers of the application,
|
||||
from low-level file operations to high-level service interactions.
|
||||
|
||||
The key features of this package are:
|
||||
- Error wrapping: The Op and an optional Msg field provide context about
|
||||
where and why an error occurred.
|
||||
- Stack traces: By wrapping errors, we can build a logical stack trace
|
||||
that is more informative than a raw stack trace.
|
||||
- Consistent error handling: Encourages a uniform approach to error
|
||||
handling across the entire codebase.
|
||||
|
||||
## Types
|
||||
|
||||
### `type Error`
|
||||
|
||||
`Error` represents a standardized error with operational context.
|
||||
|
||||
```go
|
||||
type Error struct {
|
||||
// Op is the operation being performed, e.g., "config.Load".
|
||||
Op string
|
||||
// Msg is a human-readable message explaining the error.
|
||||
Msg string
|
||||
// Err is the underlying error that was wrapped.
|
||||
Err error
|
||||
}
|
||||
```
|
||||
|
||||
#### Methods
|
||||
|
||||
- `Error() string`: Error returns the string representation of the error.
|
||||
- `Unwrap() error`: Unwrap provides compatibility for Go's errors.Is and errors.As functions.
|
||||
|
||||
## Functions
|
||||
|
||||
- `E(op, msg string, err error) error`: E is a helper function to create a new Error.
|
||||
|
||||
This is the primary way to create errors that will be consumed by the system. For example:
|
||||
|
||||
```go
|
||||
return e.E("config.Load", "failed to load config file", err)
|
||||
```
|
||||
|
||||
The `op` parameter should be in the format of `package.function` or `service.method`. The `msg` parameter should be a human-readable message that can be displayed to the user. The `err` parameter is the underlying error that is being wrapped.
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
# Help Service
|
||||
|
||||
The Help service (`pkg/help`) provides an embeddable documentation system that displays MkDocs-based help content in a dedicated window.
|
||||
|
||||
## Features
|
||||
|
||||
- Embedded help content (MkDocs static site)
|
||||
- Context-sensitive help navigation
|
||||
- Works with or without Display service
|
||||
- Multiple content sources (embedded, filesystem, custom)
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/help"
|
||||
|
||||
// Create with default embedded content
|
||||
helpService, err := help.New(help.Options{})
|
||||
|
||||
// Initialize with core dependencies
|
||||
helpService.Init(coreInstance, displayService)
|
||||
```
|
||||
|
||||
## Showing Help
|
||||
|
||||
```go
|
||||
// Show main help window
|
||||
err := helpService.Show()
|
||||
|
||||
// Show specific section
|
||||
err := helpService.ShowAt("getting-started")
|
||||
err := helpService.ShowAt("api/config")
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```go
|
||||
type Options struct {
|
||||
Source string // Path to help content directory
|
||||
Assets fs.FS // Custom filesystem for assets
|
||||
}
|
||||
```
|
||||
|
||||
### Default Embedded Content
|
||||
|
||||
```go
|
||||
// Uses embedded MkDocs site
|
||||
helpService, _ := help.New(help.Options{})
|
||||
```
|
||||
|
||||
### Custom Directory
|
||||
|
||||
```go
|
||||
// Use local directory
|
||||
helpService, _ := help.New(help.Options{
|
||||
Source: "/path/to/docs/site",
|
||||
})
|
||||
```
|
||||
|
||||
### Custom Filesystem
|
||||
|
||||
```go
|
||||
//go:embed docs/*
|
||||
var docsFS embed.FS
|
||||
|
||||
helpService, _ := help.New(help.Options{
|
||||
Assets: docsFS,
|
||||
})
|
||||
```
|
||||
|
||||
## Integration with Core
|
||||
|
||||
The help service can work standalone or integrated with Core:
|
||||
|
||||
### With Display Service
|
||||
|
||||
When Display service is available, help opens through the IPC action system:
|
||||
|
||||
```go
|
||||
// Automatically uses display.open_window action
|
||||
helpService.Init(core, displayService)
|
||||
helpService.Show()
|
||||
```
|
||||
|
||||
### Without Display Service
|
||||
|
||||
Falls back to direct Wails window creation:
|
||||
|
||||
```go
|
||||
// Creates window directly via Wails
|
||||
helpService.Init(core, nil)
|
||||
helpService.Show()
|
||||
```
|
||||
|
||||
## Lifecycle
|
||||
|
||||
```go
|
||||
// Called on application startup
|
||||
err := helpService.ServiceStartup(ctx)
|
||||
```
|
||||
|
||||
## Building Help Content
|
||||
|
||||
Help content is a static MkDocs site. To update:
|
||||
|
||||
1. Edit documentation in `docs/` directory
|
||||
2. Build with MkDocs:
|
||||
```bash
|
||||
mkdocs build
|
||||
```
|
||||
3. The built site goes to `pkg/help/public/`
|
||||
4. Content is embedded at compile time
|
||||
|
||||
## Frontend Usage (TypeScript)
|
||||
|
||||
```typescript
|
||||
import { Show, ShowAt } from '@bindings/help/service';
|
||||
|
||||
// Open help window
|
||||
await Show();
|
||||
|
||||
// Open specific section
|
||||
await ShowAt("configuration");
|
||||
await ShowAt("api/display");
|
||||
```
|
||||
|
||||
## Help Window Options
|
||||
|
||||
The help window opens with default settings:
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Title | "Help" |
|
||||
| Width | 800px |
|
||||
| Height | 600px |
|
||||
|
||||
## IPC Action
|
||||
|
||||
When using Display service, help triggers this action:
|
||||
|
||||
```go
|
||||
{
|
||||
"action": "display.open_window",
|
||||
"name": "help",
|
||||
"options": {
|
||||
"Title": "Help",
|
||||
"Width": 800,
|
||||
"Height": 600,
|
||||
"URL": "/#anchor", // When using ShowAt
|
||||
},
|
||||
}
|
||||
```
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
# I18n Service
|
||||
|
||||
The I18n service (`pkg/i18n`) provides internationalization and localization support with automatic language detection and template-based translations.
|
||||
|
||||
## Features
|
||||
|
||||
- JSON-based locale files
|
||||
- Embedded locale bundles
|
||||
- Automatic language detection from environment
|
||||
- Template variable interpolation
|
||||
- BCP 47 language tag support
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/i18n"
|
||||
|
||||
// Create service (defaults to English)
|
||||
i18n, err := i18n.New()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
## Setting Language
|
||||
|
||||
```go
|
||||
// Set language using BCP 47 tag
|
||||
err := i18n.SetLanguage("fr")
|
||||
err := i18n.SetLanguage("en-US")
|
||||
err := i18n.SetLanguage("zh-Hans")
|
||||
```
|
||||
|
||||
## Translating Messages
|
||||
|
||||
```go
|
||||
// Simple translation
|
||||
msg := i18n.Translate("welcome_message")
|
||||
|
||||
// With template data
|
||||
msg := i18n.Translate("greeting", map[string]string{
|
||||
"Name": "John",
|
||||
})
|
||||
// Template: "Hello, {{.Name}}!"
|
||||
// Result: "Hello, John!"
|
||||
```
|
||||
|
||||
## Available Languages
|
||||
|
||||
```go
|
||||
// Get list of available language codes
|
||||
langs := i18n.AvailableLanguages()
|
||||
// Returns: ["en", "es", "fr", "de", ...]
|
||||
```
|
||||
|
||||
## Get All Messages
|
||||
|
||||
```go
|
||||
// Get all translations for a language
|
||||
messages, err := i18n.GetAllMessages("en")
|
||||
for key, value := range messages {
|
||||
fmt.Printf("%s: %s\n", key, value)
|
||||
}
|
||||
```
|
||||
|
||||
## Locale File Format
|
||||
|
||||
Locale files are JSON stored in `locales/` directory:
|
||||
|
||||
```json
|
||||
// locales/en.json
|
||||
{
|
||||
"welcome": "Welcome to the application",
|
||||
"greeting": "Hello, {{.Name}}!",
|
||||
"items_count": {
|
||||
"one": "{{.Count}} item",
|
||||
"other": "{{.Count}} items"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Adding New Languages
|
||||
|
||||
1. Create a new JSON file in `pkg/i18n/locales/`:
|
||||
```
|
||||
locales/es.json
|
||||
```
|
||||
|
||||
2. Add translations:
|
||||
```json
|
||||
{
|
||||
"welcome": "Bienvenido a la aplicación",
|
||||
"greeting": "¡Hola, {{.Name}}!"
|
||||
}
|
||||
```
|
||||
|
||||
3. The service automatically loads embedded locales at startup.
|
||||
|
||||
## Language Detection
|
||||
|
||||
The service can detect system language from the `LANG` environment variable:
|
||||
|
||||
```go
|
||||
// Automatic detection happens internally
|
||||
// LANG=fr_FR.UTF-8 -> French
|
||||
// LANG=de_DE.UTF-8 -> German
|
||||
```
|
||||
|
||||
## Frontend Usage (TypeScript)
|
||||
|
||||
```typescript
|
||||
import {
|
||||
SetLanguage,
|
||||
Translate,
|
||||
AvailableLanguages,
|
||||
GetAllMessages
|
||||
} from '@bindings/i18n/service';
|
||||
|
||||
// Set language
|
||||
await SetLanguage("fr");
|
||||
|
||||
// Translate
|
||||
const welcome = await Translate("welcome_message");
|
||||
|
||||
// Get available languages for a selector
|
||||
const languages = await AvailableLanguages();
|
||||
|
||||
// Load all messages for client-side caching
|
||||
const messages = await GetAllMessages("en");
|
||||
```
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
# Installation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Go 1.22+
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install go
|
||||
|
||||
# Linux
|
||||
sudo apt install golang-go
|
||||
|
||||
# Windows - download from https://go.dev/dl/
|
||||
```
|
||||
|
||||
### Wails v3
|
||||
|
||||
```bash
|
||||
go install github.com/wailsapp/wails/v3/cmd/wails3@latest
|
||||
```
|
||||
|
||||
### Task (Build Automation)
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install go-task
|
||||
|
||||
# Linux
|
||||
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d
|
||||
|
||||
# Windows
|
||||
choco install go-task
|
||||
```
|
||||
|
||||
## Install Core
|
||||
|
||||
```bash
|
||||
go get github.com/Snider/Core@latest
|
||||
```
|
||||
|
||||
## Verify Installation
|
||||
|
||||
```bash
|
||||
# Check Go
|
||||
go version
|
||||
|
||||
# Check Wails
|
||||
wails3 version
|
||||
|
||||
# Check Task
|
||||
task --version
|
||||
```
|
||||
|
||||
## IDE Setup
|
||||
|
||||
### VS Code
|
||||
|
||||
Install the Go extension and configure:
|
||||
|
||||
```json
|
||||
{
|
||||
"go.useLanguageServer": true,
|
||||
"gopls": {
|
||||
"ui.semanticTokens": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GoLand / IntelliJ
|
||||
|
||||
Go support is built-in. Enable the Wails plugin for additional features.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Continue to [Quick Start](quickstart.md) to create your first application.
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
# IO Service
|
||||
|
||||
The IO package (`pkg/io`) provides a unified interface for file operations across different storage backends (local filesystem, S3, SFTP, etc.).
|
||||
|
||||
## Features
|
||||
|
||||
- Abstract `Medium` interface for storage backends
|
||||
- Local filesystem implementation
|
||||
- Copy between different mediums
|
||||
- Mock implementation for testing
|
||||
|
||||
## Medium Interface
|
||||
|
||||
All storage backends implement the `Medium` interface:
|
||||
|
||||
```go
|
||||
type Medium interface {
|
||||
Read(path string) (string, error)
|
||||
Write(path, content string) error
|
||||
EnsureDir(path string) error
|
||||
IsFile(path string) bool
|
||||
FileGet(path string) (string, error)
|
||||
FileSet(path, content string) error
|
||||
}
|
||||
```
|
||||
|
||||
## Local Filesystem
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/Snider/Core/pkg/io"
|
||||
"github.com/Snider/Core/pkg/io/local"
|
||||
)
|
||||
|
||||
// Pre-initialized global medium (root = "/")
|
||||
content, err := io.Local.Read("/etc/hosts")
|
||||
|
||||
// Create sandboxed medium
|
||||
medium, err := local.New("/app/data")
|
||||
content, err := medium.Read("config.json") // Reads /app/data/config.json
|
||||
```
|
||||
|
||||
## Basic Operations
|
||||
|
||||
```go
|
||||
// Read file
|
||||
content, err := medium.Read("path/to/file.txt")
|
||||
|
||||
// Write file
|
||||
err := medium.Write("path/to/file.txt", "content")
|
||||
|
||||
// Check if file exists
|
||||
if medium.IsFile("config.json") {
|
||||
// File exists
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
err := medium.EnsureDir("path/to/dir")
|
||||
|
||||
// Convenience methods
|
||||
content, err := medium.FileGet("file.txt")
|
||||
err := medium.FileSet("file.txt", "content")
|
||||
```
|
||||
|
||||
## Helper Functions
|
||||
|
||||
Package-level functions that work with any Medium:
|
||||
|
||||
```go
|
||||
// Read from medium
|
||||
content, err := io.Read(medium, "file.txt")
|
||||
|
||||
// Write to medium
|
||||
err := io.Write(medium, "file.txt", "content")
|
||||
|
||||
// Ensure directory
|
||||
err := io.EnsureDir(medium, "path/to/dir")
|
||||
|
||||
// Check if file
|
||||
exists := io.IsFile(medium, "file.txt")
|
||||
```
|
||||
|
||||
## Copy Between Mediums
|
||||
|
||||
```go
|
||||
localMedium, _ := local.New("/local/path")
|
||||
remoteMedium := s3.New(bucket, region) // hypothetical S3 implementation
|
||||
|
||||
// Copy from local to remote
|
||||
err := io.Copy(localMedium, "data.json", remoteMedium, "backup/data.json")
|
||||
```
|
||||
|
||||
## Mock Medium for Testing
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/io"
|
||||
|
||||
func TestMyFunction(t *testing.T) {
|
||||
mock := io.NewMockMedium()
|
||||
|
||||
// Pre-populate files
|
||||
mock.Files["config.json"] = `{"key": "value"}`
|
||||
mock.Dirs["data"] = true
|
||||
|
||||
// Use in tests
|
||||
myService := NewService(mock)
|
||||
|
||||
// Verify writes
|
||||
err := myService.SaveData("test")
|
||||
if mock.Files["data/test.json"] != expectedContent {
|
||||
t.Error("unexpected content")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Creating Custom Backends
|
||||
|
||||
Implement the `Medium` interface for custom storage:
|
||||
|
||||
```go
|
||||
type S3Medium struct {
|
||||
bucket string
|
||||
client *s3.Client
|
||||
}
|
||||
|
||||
func (m *S3Medium) Read(path string) (string, error) {
|
||||
// Implement S3 read
|
||||
}
|
||||
|
||||
func (m *S3Medium) Write(path, content string) error {
|
||||
// Implement S3 write
|
||||
}
|
||||
|
||||
// ... implement remaining methods
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```go
|
||||
content, err := medium.Read("missing.txt")
|
||||
if err != nil {
|
||||
// File not found or read error
|
||||
log.Printf("Read failed: %v", err)
|
||||
}
|
||||
```
|
||||
|
||||
## Frontend Usage
|
||||
|
||||
The IO package is primarily used server-side. Frontend file operations should use the Display service dialogs or direct API calls:
|
||||
|
||||
```typescript
|
||||
import { OpenFileDialog, SaveFileDialog } from '@bindings/display/service';
|
||||
|
||||
// Open file picker
|
||||
const path = await OpenFileDialog({
|
||||
title: "Select File",
|
||||
filters: [{ displayName: "Text", pattern: "*.txt" }]
|
||||
});
|
||||
|
||||
// Save file picker
|
||||
const savePath = await SaveFileDialog({
|
||||
title: "Save As",
|
||||
defaultFilename: "document.txt"
|
||||
});
|
||||
```
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
# IPC & Actions
|
||||
|
||||
Core provides an inter-process communication system for services to communicate without tight coupling.
|
||||
|
||||
## Message Structure
|
||||
|
||||
```go
|
||||
type Message struct {
|
||||
Type string // Message type identifier
|
||||
Data map[string]any // Message payload
|
||||
Source string // Originating service (optional)
|
||||
Timestamp time.Time // When message was created
|
||||
}
|
||||
```
|
||||
|
||||
## Sending Messages
|
||||
|
||||
```go
|
||||
c.ACTION(core.Message{
|
||||
Type: "user.created",
|
||||
Data: map[string]any{
|
||||
"id": "123",
|
||||
"email": "user@example.com",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Handling Messages
|
||||
|
||||
Register action handlers during service initialization:
|
||||
|
||||
```go
|
||||
func NewNotificationService(c *core.Core) (any, error) {
|
||||
svc := &NotificationService{}
|
||||
|
||||
// Register handler
|
||||
c.RegisterAction(func(c *core.Core, msg core.Message) error {
|
||||
return svc.handleAction(msg)
|
||||
})
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
func (s *NotificationService) handleAction(msg core.Message) error {
|
||||
switch msg.Type {
|
||||
case "user.created":
|
||||
email := msg.Data["email"].(string)
|
||||
return s.sendWelcomeEmail(email)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Auto-Discovery
|
||||
|
||||
Services implementing `HandleIPCEvents` are automatically registered:
|
||||
|
||||
```go
|
||||
type MyService struct{}
|
||||
|
||||
// Automatically registered when using WithService
|
||||
func (s *MyService) HandleIPCEvents(c *core.Core, msg core.Message) error {
|
||||
// Handle messages
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Request/Response
|
||||
|
||||
```go
|
||||
// Sender
|
||||
responseChan := make(chan any)
|
||||
c.ACTION(core.Message{
|
||||
Type: "data.request",
|
||||
Data: map[string]any{
|
||||
"query": "SELECT * FROM users",
|
||||
"response": responseChan,
|
||||
},
|
||||
})
|
||||
result := <-responseChan
|
||||
|
||||
// Handler
|
||||
func (s *DataService) handleAction(msg core.Message) error {
|
||||
if msg.Type == "data.request" {
|
||||
query := msg.Data["query"].(string)
|
||||
respChan := msg.Data["response"].(chan any)
|
||||
|
||||
result, err := s.execute(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
respChan <- result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Event Broadcasting
|
||||
|
||||
```go
|
||||
// Broadcast to all listeners
|
||||
c.ACTION(core.Message{
|
||||
Type: "system.config.changed",
|
||||
Data: map[string]any{
|
||||
"key": "theme",
|
||||
"value": "dark",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use namespaced types** - `service.action` format
|
||||
2. **Keep payloads simple** - Use primitive types when possible
|
||||
3. **Handle errors** - Return errors from handlers
|
||||
4. **Document message types** - Create constants for message types
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
# Service Lifecycle
|
||||
|
||||
Core provides lifecycle hooks for services to initialize and clean up resources.
|
||||
|
||||
## Lifecycle Interfaces
|
||||
|
||||
### Startable
|
||||
|
||||
Called when the application starts:
|
||||
|
||||
```go
|
||||
type Startable interface {
|
||||
OnStartup(ctx context.Context) error
|
||||
}
|
||||
```
|
||||
|
||||
### Stoppable
|
||||
|
||||
Called when the application shuts down:
|
||||
|
||||
```go
|
||||
type Stoppable interface {
|
||||
OnShutdown(ctx context.Context) error
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Example
|
||||
|
||||
```go
|
||||
type DatabaseService struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (s *DatabaseService) OnStartup(ctx context.Context) error {
|
||||
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify connection
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.db = db
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DatabaseService) OnShutdown(ctx context.Context) error {
|
||||
if s.db != nil {
|
||||
return s.db.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Lifecycle Order
|
||||
|
||||
1. **Registration**: Services registered via `core.New()`
|
||||
2. **Wails Binding**: Services bound to Wails app
|
||||
3. **Startup**: `OnStartup()` called for each Startable service
|
||||
4. **Running**: Application runs
|
||||
5. **Shutdown**: `OnShutdown()` called for each Stoppable service
|
||||
|
||||
## Context Usage
|
||||
|
||||
The context passed to lifecycle methods includes:
|
||||
|
||||
- Cancellation signal for graceful shutdown
|
||||
- Deadline for timeout handling
|
||||
|
||||
```go
|
||||
func (s *Service) OnStartup(ctx context.Context) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-s.initialize():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
If `OnStartup` returns an error, the application will fail to start:
|
||||
|
||||
```go
|
||||
func (s *Service) OnStartup(ctx context.Context) error {
|
||||
if err := s.validate(); err != nil {
|
||||
return fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Keep startup fast** - Defer heavy initialization
|
||||
2. **Handle context cancellation** - Support graceful shutdown
|
||||
3. **Clean up resources** - Always implement OnShutdown for services with resources
|
||||
4. **Log lifecycle events** - Helps with debugging
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
# MCP Bridge
|
||||
|
||||
The MCP Bridge (`cmd/core-gui/mcp_bridge.go`) connects the Model Context Protocol server with Display, WebView, and WebSocket services.
|
||||
|
||||
## Overview
|
||||
|
||||
The MCP Bridge provides an HTTP API for AI assistants to interact with the desktop application, enabling:
|
||||
|
||||
- Window and screen management
|
||||
- JavaScript execution in webviews
|
||||
- DOM interaction (click, type, select)
|
||||
- Screenshot capture
|
||||
- File and process management
|
||||
- Real-time events via WebSocket
|
||||
|
||||
## HTTP Endpoints
|
||||
|
||||
| Endpoint | Description |
|
||||
|----------|-------------|
|
||||
| `GET /health` | Health check |
|
||||
| `GET /mcp` | Server capabilities |
|
||||
| `GET /mcp/tools` | List available tools |
|
||||
| `POST /mcp/call` | Execute a tool |
|
||||
| `WS /ws` | WebSocket for GUI clients |
|
||||
| `WS /events` | WebSocket for display events |
|
||||
|
||||
## Server Capabilities
|
||||
|
||||
```bash
|
||||
curl http://localhost:9877/mcp
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "core",
|
||||
"version": "0.1.0",
|
||||
"capabilities": {
|
||||
"webview": true,
|
||||
"display": true,
|
||||
"windowControl": true,
|
||||
"screenControl": true,
|
||||
"websocket": "ws://localhost:9877/ws",
|
||||
"events": "ws://localhost:9877/events"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Tool Categories
|
||||
|
||||
### File Operations
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `file_read` | Read file contents |
|
||||
| `file_write` | Write content to file |
|
||||
| `file_edit` | Edit file by replacing text |
|
||||
| `file_delete` | Delete a file |
|
||||
| `file_exists` | Check if file exists |
|
||||
| `dir_list` | List directory contents |
|
||||
| `dir_create` | Create directory |
|
||||
|
||||
### Window Control
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `window_list` | List all windows |
|
||||
| `window_create` | Create new window |
|
||||
| `window_close` | Close window |
|
||||
| `window_position` | Move window |
|
||||
| `window_size` | Resize window |
|
||||
| `window_maximize` | Maximize window |
|
||||
| `window_minimize` | Minimize window |
|
||||
| `window_focus` | Bring window to front |
|
||||
|
||||
### WebView Interaction
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `webview_eval` | Execute JavaScript |
|
||||
| `webview_click` | Click element |
|
||||
| `webview_type` | Type into element |
|
||||
| `webview_screenshot` | Capture page |
|
||||
| `webview_navigate` | Navigate to URL |
|
||||
| `webview_console` | Get console messages |
|
||||
|
||||
### Screen Management
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `screen_list` | List all monitors |
|
||||
| `screen_primary` | Get primary screen |
|
||||
| `screen_at_point` | Get screen at coordinates |
|
||||
| `screen_work_areas` | Get usable screen space |
|
||||
|
||||
### Layout Management
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `layout_save` | Save window arrangement |
|
||||
| `layout_restore` | Restore saved layout |
|
||||
| `layout_tile` | Auto-tile windows |
|
||||
| `layout_snap` | Snap window to edge |
|
||||
|
||||
## Calling Tools
|
||||
|
||||
```bash
|
||||
# List windows
|
||||
curl -X POST http://localhost:9877/mcp/call \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"tool": "window_list", "params": {}}'
|
||||
|
||||
# Move window
|
||||
curl -X POST http://localhost:9877/mcp/call \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tool": "window_position",
|
||||
"params": {"name": "main", "x": 100, "y": 100}
|
||||
}'
|
||||
|
||||
# Execute JavaScript
|
||||
curl -X POST http://localhost:9877/mcp/call \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tool": "webview_eval",
|
||||
"params": {
|
||||
"window": "main",
|
||||
"code": "document.title"
|
||||
}
|
||||
}'
|
||||
|
||||
# Click element
|
||||
curl -X POST http://localhost:9877/mcp/call \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tool": "webview_click",
|
||||
"params": {
|
||||
"window": "main",
|
||||
"selector": "#submit-button"
|
||||
}
|
||||
}'
|
||||
|
||||
# Take screenshot
|
||||
curl -X POST http://localhost:9877/mcp/call \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tool": "webview_screenshot",
|
||||
"params": {"window": "main"}
|
||||
}'
|
||||
```
|
||||
|
||||
## WebSocket Events
|
||||
|
||||
Connect to `/events` for real-time display events:
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://localhost:9877/events');
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
switch (data.type) {
|
||||
case 'window.focus':
|
||||
console.log('Window focused:', data.name);
|
||||
break;
|
||||
case 'window.move':
|
||||
console.log('Window moved:', data.name, data.x, data.y);
|
||||
break;
|
||||
case 'theme.change':
|
||||
console.log('Theme changed:', data.isDark);
|
||||
break;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Event types:
|
||||
|
||||
- `window.focus` - Window received focus
|
||||
- `window.blur` - Window lost focus
|
||||
- `window.move` - Window position changed
|
||||
- `window.resize` - Window size changed
|
||||
- `window.close` - Window was closed
|
||||
- `window.create` - New window created
|
||||
- `theme.change` - System theme changed
|
||||
- `screen.change` - Screen configuration changed
|
||||
|
||||
## Go Integration
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/cmd/core-gui"
|
||||
|
||||
// Create bridge
|
||||
bridge := NewMCPBridge(9877, displayService)
|
||||
|
||||
// Access services
|
||||
mcpSvc := bridge.GetMCPService()
|
||||
webview := bridge.GetWebView()
|
||||
display := bridge.GetDisplay()
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The bridge starts automatically on Wails app startup via the `ServiceStartup` lifecycle hook:
|
||||
|
||||
```go
|
||||
func (b *MCPBridge) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
b.app = application.Get()
|
||||
b.webview.SetApp(b.app)
|
||||
go b.startHTTPServer()
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
The MCP server binds to localhost only by default. For production:
|
||||
|
||||
- Consider firewall rules
|
||||
- Add authentication if needed
|
||||
- Limit exposed tools
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
# MCP Service
|
||||
|
||||
The MCP service (`pkg/mcp`) implements the [Model Context Protocol](https://modelcontextprotocol.io/), enabling AI assistants like Claude to interact with your application.
|
||||
|
||||
## Overview
|
||||
|
||||
MCP provides a standardized way for AI tools to:
|
||||
|
||||
- Execute operations in your application
|
||||
- Query application state
|
||||
- Interact with the UI
|
||||
- Manage files and processes
|
||||
|
||||
## Basic Setup
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/mcp"
|
||||
|
||||
// Create standalone MCP server
|
||||
mcpService := mcp.NewStandaloneWithPort(9877)
|
||||
|
||||
// Or integrate with Core
|
||||
c, _ := core.New(
|
||||
core.WithService(mcp.NewService),
|
||||
)
|
||||
```
|
||||
|
||||
## Available Tools
|
||||
|
||||
The MCP service exposes numerous tools organized by category:
|
||||
|
||||
### File Operations
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `file_read` | Read file contents |
|
||||
| `file_write` | Write content to file |
|
||||
| `file_edit` | Replace text in file |
|
||||
| `file_delete` | Delete a file |
|
||||
| `file_exists` | Check if file exists |
|
||||
| `dir_list` | List directory contents |
|
||||
| `dir_create` | Create directory |
|
||||
|
||||
### Window Control
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `window_list` | List all windows |
|
||||
| `window_create` | Create new window |
|
||||
| `window_close` | Close window |
|
||||
| `window_position` | Move window |
|
||||
| `window_size` | Resize window |
|
||||
| `window_maximize` | Maximize window |
|
||||
| `window_minimize` | Minimize window |
|
||||
| `window_focus` | Bring to front |
|
||||
|
||||
### WebView Interaction
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `webview_eval` | Execute JavaScript |
|
||||
| `webview_click` | Click element |
|
||||
| `webview_type` | Type into element |
|
||||
| `webview_screenshot` | Capture page |
|
||||
| `webview_navigate` | Navigate to URL |
|
||||
| `webview_console` | Get console logs |
|
||||
|
||||
### Process Management
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `process_start` | Start a process |
|
||||
| `process_stop` | Stop a process |
|
||||
| `process_list` | List running processes |
|
||||
| `process_output` | Get process output |
|
||||
|
||||
## HTTP API
|
||||
|
||||
The MCP service exposes an HTTP API:
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:9877/health
|
||||
|
||||
# List available tools
|
||||
curl http://localhost:9877/mcp/tools
|
||||
|
||||
# Call a tool
|
||||
curl -X POST http://localhost:9877/mcp/call \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"tool": "window_list", "params": {}}'
|
||||
```
|
||||
|
||||
## WebSocket Events
|
||||
|
||||
Connect to `/events` for real-time updates:
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://localhost:9877/events');
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('Event:', data.type, data.data);
|
||||
};
|
||||
```
|
||||
|
||||
## Integration with Display Service
|
||||
|
||||
```go
|
||||
mcpService := mcp.NewStandaloneWithPort(9877)
|
||||
mcpService.SetDisplay(displayService)
|
||||
mcpService.SetWebView(webviewService)
|
||||
```
|
||||
|
||||
## Example: Claude Integration
|
||||
|
||||
When Claude connects via MCP, it can:
|
||||
|
||||
```
|
||||
User: "Move the settings window to the left side of the screen"
|
||||
|
||||
Claude uses: window_position("settings", 0, 100)
|
||||
```
|
||||
|
||||
```
|
||||
User: "Take a screenshot of the app"
|
||||
|
||||
Claude uses: webview_screenshot("main")
|
||||
```
|
||||
|
||||
```
|
||||
User: "Click the submit button"
|
||||
|
||||
Claude uses: webview_click("main", "#submit-btn")
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- MCP server binds to localhost by default
|
||||
- No authentication (designed for local AI assistants)
|
||||
- Consider firewall rules for production
|
||||
|
||||
## Configuration
|
||||
|
||||
```go
|
||||
// Custom port
|
||||
mcp.NewStandaloneWithPort(8080)
|
||||
|
||||
// With all services
|
||||
bridge := NewMCPBridge(9877, displayService)
|
||||
bridge.SetWebView(webviewService)
|
||||
```
|
||||
|
|
@ -1,271 +0,0 @@
|
|||
# Module System
|
||||
|
||||
The Module system (`pkg/module`) provides a declarative way to register UI menus, routes, and API endpoints using the `.itw3.json` configuration format.
|
||||
|
||||
## Features
|
||||
|
||||
- Declarative module configuration
|
||||
- UI menu contributions
|
||||
- Frontend route registration
|
||||
- API endpoint declarations
|
||||
- Multi-context support (developer, retail, miner)
|
||||
- Binary/daemon management
|
||||
- Module dependencies
|
||||
|
||||
## Module Config Format
|
||||
|
||||
Modules are defined using `.itw3.json` files:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "wallet",
|
||||
"type": "core",
|
||||
"name": "Wallet Manager",
|
||||
"version": "1.0.0",
|
||||
"namespace": "finance",
|
||||
"description": "Cryptocurrency wallet management",
|
||||
"author": "Your Name",
|
||||
"contexts": ["default", "retail"],
|
||||
"menu": [...],
|
||||
"routes": [...],
|
||||
"api": [...],
|
||||
"config": {...}
|
||||
}
|
||||
```
|
||||
|
||||
## Module Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `core` | Built-in core functionality |
|
||||
| `app` | External web application |
|
||||
| `bin` | Binary/daemon wrapper |
|
||||
|
||||
## UI Contexts
|
||||
|
||||
Modules can target specific UI contexts:
|
||||
|
||||
| Context | Description |
|
||||
|---------|-------------|
|
||||
| `default` | Standard user interface |
|
||||
| `developer` | Developer tools and debugging |
|
||||
| `retail` | Point-of-sale interface |
|
||||
| `miner` | Mining operations interface |
|
||||
|
||||
## Menu Contributions
|
||||
|
||||
Add items to the application menu:
|
||||
|
||||
```json
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"id": "wallet-send",
|
||||
"label": "Send Funds",
|
||||
"icon": "send",
|
||||
"route": "/wallet/send",
|
||||
"accelerator": "CmdOrCtrl+Shift+S",
|
||||
"contexts": ["default", "retail"],
|
||||
"order": 10
|
||||
},
|
||||
{
|
||||
"id": "wallet-receive",
|
||||
"label": "Receive",
|
||||
"icon": "receive",
|
||||
"route": "/wallet/receive",
|
||||
"order": 20
|
||||
},
|
||||
{
|
||||
"separator": true
|
||||
},
|
||||
{
|
||||
"id": "wallet-settings",
|
||||
"label": "Settings",
|
||||
"action": "wallet.open_settings",
|
||||
"children": [
|
||||
{"id": "wallet-backup", "label": "Backup", "action": "wallet.backup"},
|
||||
{"id": "wallet-restore", "label": "Restore", "action": "wallet.restore"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Route Contributions
|
||||
|
||||
Register frontend routes:
|
||||
|
||||
```json
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"path": "/wallet",
|
||||
"component": "wallet-dashboard",
|
||||
"title": "Wallet",
|
||||
"icon": "wallet",
|
||||
"contexts": ["default"]
|
||||
},
|
||||
{
|
||||
"path": "/wallet/send",
|
||||
"component": "wallet-send-form",
|
||||
"title": "Send Funds"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## API Declarations
|
||||
|
||||
Declare API endpoints the module provides:
|
||||
|
||||
```json
|
||||
{
|
||||
"api": [
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/balance",
|
||||
"description": "Get wallet balance"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/send",
|
||||
"description": "Send transaction"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Binary Downloads
|
||||
|
||||
For `bin` type modules, specify platform binaries:
|
||||
|
||||
```json
|
||||
{
|
||||
"downloads": {
|
||||
"app": "https://example.com/wallet-ui.tar.gz",
|
||||
"x86_64": {
|
||||
"darwin": {
|
||||
"url": "https://example.com/wallet-darwin-x64",
|
||||
"checksum": "sha256:abc123..."
|
||||
},
|
||||
"linux": {
|
||||
"url": "https://example.com/wallet-linux-x64",
|
||||
"checksum": "sha256:def456..."
|
||||
},
|
||||
"windows": {
|
||||
"url": "https://example.com/wallet-win-x64.exe",
|
||||
"checksum": "sha256:ghi789..."
|
||||
}
|
||||
},
|
||||
"aarch64": {
|
||||
"darwin": {
|
||||
"url": "https://example.com/wallet-darwin-arm64"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Web App Configuration
|
||||
|
||||
For `app` type modules:
|
||||
|
||||
```json
|
||||
{
|
||||
"app": {
|
||||
"url": "https://example.com/wallet-app.tar.gz",
|
||||
"type": "spa",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "rename",
|
||||
"from": "dist",
|
||||
"to": "wallet"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
Declare module dependencies:
|
||||
|
||||
```json
|
||||
{
|
||||
"depends": ["core", "crypto"]
|
||||
}
|
||||
```
|
||||
|
||||
## Using in Go
|
||||
|
||||
### Module Registration
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/module"
|
||||
|
||||
// Create from config
|
||||
cfg := module.Config{
|
||||
Code: "wallet",
|
||||
Type: module.TypeCore,
|
||||
Name: "Wallet",
|
||||
Namespace: "finance",
|
||||
}
|
||||
|
||||
mod := module.Module{
|
||||
Config: cfg,
|
||||
Handler: myHandler,
|
||||
}
|
||||
```
|
||||
|
||||
### Gin Router Integration
|
||||
|
||||
```go
|
||||
type WalletModule struct{}
|
||||
|
||||
func (m *WalletModule) RegisterRoutes(group *gin.RouterGroup) {
|
||||
group.GET("/balance", m.getBalance)
|
||||
group.POST("/send", m.sendTransaction)
|
||||
}
|
||||
|
||||
// Register with Gin
|
||||
router := gin.Default()
|
||||
apiGroup := router.Group("/api/finance/wallet")
|
||||
walletModule.RegisterRoutes(apiGroup)
|
||||
```
|
||||
|
||||
## Registry Service
|
||||
|
||||
The registry manages all modules:
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/module"
|
||||
|
||||
registry := module.NewRegistry()
|
||||
|
||||
// Register module
|
||||
registry.Register(walletModule)
|
||||
|
||||
// Get module by code
|
||||
mod := registry.Get("wallet")
|
||||
|
||||
// List all modules
|
||||
modules := registry.List()
|
||||
|
||||
// Get modules for context
|
||||
devModules := registry.ForContext(module.ContextDeveloper)
|
||||
```
|
||||
|
||||
## Built-in Modules
|
||||
|
||||
Core provides several built-in modules:
|
||||
|
||||
- System information
|
||||
- Configuration management
|
||||
- Process management
|
||||
- File operations
|
||||
|
||||
Access via:
|
||||
|
||||
```go
|
||||
builtins := module.BuiltinModules()
|
||||
```
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
# GUI Application
|
||||
|
||||
The Core GUI (`cmd/core-gui`) is a Wails v3 desktop application that demonstrates the Core framework capabilities with integrated MCP support.
|
||||
|
||||
## Features
|
||||
|
||||
- Angular frontend with Wails bindings
|
||||
- MCP HTTP server for AI tool integration
|
||||
- WebView automation capabilities
|
||||
- Real-time WebSocket communication
|
||||
- System tray support
|
||||
- Multi-window management
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Angular Frontend │
|
||||
│ (TypeScript + Wails Bindings) │
|
||||
└─────────────────┬───────────────────────┘
|
||||
│ IPC
|
||||
┌─────────────────┴───────────────────────┐
|
||||
│ Wails Runtime │
|
||||
│ (Window, Events, Bindings) │
|
||||
└─────────────────┬───────────────────────┘
|
||||
│
|
||||
┌─────────────────┴───────────────────────┐
|
||||
│ MCP Bridge │
|
||||
│ ┌─────────┬──────────┬─────────────┐ │
|
||||
│ │ Display │ WebView │ WebSocket │ │
|
||||
│ │ Service │ Service │ Hub │ │
|
||||
│ └─────────┴──────────┴─────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Running the GUI
|
||||
|
||||
### Development Mode
|
||||
|
||||
```bash
|
||||
# From project root
|
||||
task gui:dev
|
||||
|
||||
# Or directly
|
||||
cd cmd/core-gui
|
||||
wails3 dev
|
||||
```
|
||||
|
||||
### Production Build
|
||||
|
||||
```bash
|
||||
task gui:build
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
cmd/core-gui/
|
||||
├── main.go # Application entry point
|
||||
├── mcp_bridge.go # MCP HTTP server and tool handler
|
||||
├── claude_bridge.go # Claude MCP client (optional)
|
||||
├── frontend/ # Angular application
|
||||
│ ├── src/
|
||||
│ │ ├── app/ # Angular components
|
||||
│ │ └── lib/ # Shared utilities
|
||||
│ └── bindings/ # Generated Wails bindings
|
||||
└── public/ # Static assets
|
||||
```
|
||||
|
||||
## Services Integrated
|
||||
|
||||
The GUI integrates several Core services:
|
||||
|
||||
| Service | Purpose |
|
||||
|---------|---------|
|
||||
| Display | Window management, dialogs, tray |
|
||||
| WebView | JavaScript execution, DOM interaction |
|
||||
| MCP | AI tool protocol server |
|
||||
| WebSocket | Real-time communication |
|
||||
|
||||
## Configuration
|
||||
|
||||
The application uses the Config service for settings:
|
||||
|
||||
```go
|
||||
// Default settings
|
||||
DefaultRoute: "/"
|
||||
Language: "en"
|
||||
Features: []
|
||||
```
|
||||
|
||||
## Frontend Bindings
|
||||
|
||||
Wails generates TypeScript bindings for Go services:
|
||||
|
||||
```typescript
|
||||
import { CreateWindow, ShowNotification } from '@bindings/display/service';
|
||||
import { Translate, SetLanguage } from '@bindings/i18n/service';
|
||||
|
||||
// Create a new window
|
||||
await CreateWindow({
|
||||
name: "settings",
|
||||
title: "Settings",
|
||||
width: 800,
|
||||
height: 600
|
||||
});
|
||||
|
||||
// Show notification
|
||||
await ShowNotification({
|
||||
title: "Success",
|
||||
message: "Operation completed!"
|
||||
});
|
||||
```
|
||||
|
||||
## WebSocket Communication
|
||||
|
||||
Connect to the WebSocket endpoint for real-time updates:
|
||||
|
||||
```typescript
|
||||
const ws = new WebSocket('ws://localhost:9877/ws');
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('Received:', data);
|
||||
};
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
type: 'ping',
|
||||
data: {}
|
||||
}));
|
||||
```
|
||||
|
||||
## System Tray
|
||||
|
||||
The application includes system tray support:
|
||||
|
||||
```go
|
||||
// Set tray menu
|
||||
display.SetTrayMenu([]display.TrayMenuItem{
|
||||
{Label: "Open", ActionID: "open"},
|
||||
{Label: "Settings", ActionID: "settings"},
|
||||
{IsSeparator: true},
|
||||
{Label: "Quit", ActionID: "quit"},
|
||||
})
|
||||
```
|
||||
|
||||
## Building for Distribution
|
||||
|
||||
### macOS
|
||||
|
||||
```bash
|
||||
task gui:build
|
||||
# Creates: build/bin/core-gui.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
```bash
|
||||
task gui:build
|
||||
# Creates: build/bin/core-gui.exe
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
```bash
|
||||
task gui:build
|
||||
# Creates: build/bin/core-gui
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `MCP_PORT` | MCP server port (default: 9877) |
|
||||
| `DEBUG` | Enable debug logging |
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
# Plugin System
|
||||
|
||||
The Plugin system (`pkg/plugin`) allows you to extend Core applications with HTTP-based plugins that register routes under `/api/{namespace}/{name}/`.
|
||||
|
||||
## Features
|
||||
|
||||
- Namespace-based organization
|
||||
- HTTP handler registration
|
||||
- Lifecycle hooks (OnRegister, OnUnregister)
|
||||
- Wails service integration
|
||||
|
||||
## Plugin Interface
|
||||
|
||||
All plugins implement the `Plugin` interface:
|
||||
|
||||
```go
|
||||
type Plugin interface {
|
||||
// Name returns the unique identifier for this plugin
|
||||
Name() string
|
||||
|
||||
// Namespace returns the plugin's namespace (e.g., "core", "mining")
|
||||
Namespace() string
|
||||
|
||||
// ServeHTTP handles HTTP requests routed to this plugin
|
||||
http.Handler
|
||||
|
||||
// OnRegister is called when the plugin is registered
|
||||
OnRegister(ctx context.Context) error
|
||||
|
||||
// OnUnregister is called when the plugin is being removed
|
||||
OnUnregister(ctx context.Context) error
|
||||
}
|
||||
```
|
||||
|
||||
## Using BasePlugin
|
||||
|
||||
For simple plugins, embed `BasePlugin`:
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/plugin"
|
||||
|
||||
func NewMyPlugin() *plugin.BasePlugin {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("Hello from plugin!"))
|
||||
})
|
||||
|
||||
return plugin.NewBasePlugin("myapp", "greeting", handler).
|
||||
WithDescription("A simple greeting plugin").
|
||||
WithVersion("1.0.0")
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Plugin Implementation
|
||||
|
||||
For more control, implement the full interface:
|
||||
|
||||
```go
|
||||
type DataPlugin struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (p *DataPlugin) Name() string { return "data" }
|
||||
func (p *DataPlugin) Namespace() string { return "myapp" }
|
||||
|
||||
func (p *DataPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/users":
|
||||
p.handleUsers(w, r)
|
||||
case "/items":
|
||||
p.handleItems(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *DataPlugin) OnRegister(ctx context.Context) error {
|
||||
// Initialize database connection
|
||||
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.db = db
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DataPlugin) OnUnregister(ctx context.Context) error {
|
||||
if p.db != nil {
|
||||
return p.db.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Plugin Info
|
||||
|
||||
Access plugin metadata:
|
||||
|
||||
```go
|
||||
info := myPlugin.Info()
|
||||
fmt.Println(info.Name) // "greeting"
|
||||
fmt.Println(info.Namespace) // "myapp"
|
||||
fmt.Println(info.Description) // "A simple greeting plugin"
|
||||
fmt.Println(info.Version) // "1.0.0"
|
||||
```
|
||||
|
||||
## Wails Integration
|
||||
|
||||
Register plugins as Wails services:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewServiceWithOptions(
|
||||
myPlugin,
|
||||
plugin.ServiceOptionsForPlugin(myPlugin),
|
||||
),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## URL Routing
|
||||
|
||||
Plugins receive requests at:
|
||||
|
||||
```
|
||||
/api/{namespace}/{name}/{path}
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `/api/myapp/greeting/` → GreetingPlugin
|
||||
- `/api/myapp/data/users` → DataPlugin (path: "/users")
|
||||
- `/api/core/system/health` → SystemPlugin (path: "/health")
|
||||
|
||||
## Built-in Plugins
|
||||
|
||||
### System Plugin
|
||||
|
||||
Located at `pkg/plugin/builtin/system`:
|
||||
|
||||
```go
|
||||
// Provides system information endpoints
|
||||
/api/core/system/info - Application info
|
||||
/api/core/system/health - Health check
|
||||
```
|
||||
|
||||
## Plugin Router
|
||||
|
||||
The Router manages plugin registration:
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/plugin"
|
||||
|
||||
router := plugin.NewRouter()
|
||||
|
||||
// Register plugins
|
||||
router.Register(ctx, myPlugin)
|
||||
router.Register(ctx, dataPlugin)
|
||||
|
||||
// Get all registered plugins
|
||||
plugins := router.List()
|
||||
|
||||
// Unregister a plugin
|
||||
router.Unregister(ctx, "myapp", "greeting")
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use namespaces** to group related plugins
|
||||
2. **Implement OnRegister** for initialization that can fail
|
||||
3. **Implement OnUnregister** to clean up resources
|
||||
4. **Return meaningful errors** from lifecycle hooks
|
||||
5. **Use standard HTTP patterns** in ServeHTTP
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
# Quick Start
|
||||
|
||||
Build a simple Core application in 5 minutes.
|
||||
|
||||
## Create Project
|
||||
|
||||
```bash
|
||||
mkdir myapp && cd myapp
|
||||
go mod init myapp
|
||||
```
|
||||
|
||||
## Install Dependencies
|
||||
|
||||
```bash
|
||||
go get github.com/Snider/Core@latest
|
||||
go get github.com/wailsapp/wails/v3@latest
|
||||
```
|
||||
|
||||
## Create Main File
|
||||
|
||||
Create `main.go`:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"log"
|
||||
|
||||
"github.com/Snider/Core/pkg/core"
|
||||
"github.com/Snider/Core/pkg/display"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed all:frontend/dist
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
// Initialize Core with display service
|
||||
c, err := core.New(
|
||||
core.WithAssets(assets),
|
||||
core.WithService(display.NewService),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Get display service for window creation
|
||||
displaySvc := core.MustServiceFor[*display.Service](c, "display")
|
||||
|
||||
// Create Wails application
|
||||
app := application.New(application.Options{
|
||||
Name: "My App",
|
||||
Assets: application.AssetOptions{
|
||||
FS: assets,
|
||||
},
|
||||
})
|
||||
|
||||
// Create main window
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "My App",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
URL: "/",
|
||||
})
|
||||
|
||||
// Register display service with Wails
|
||||
app.RegisterService(displaySvc)
|
||||
|
||||
// Run application
|
||||
if err := app.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Create Frontend
|
||||
|
||||
Create a minimal frontend:
|
||||
|
||||
```bash
|
||||
mkdir -p frontend/dist
|
||||
```
|
||||
|
||||
Create `frontend/dist/index.html`:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My App</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background: #1a1a2e;
|
||||
color: #eee;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello from Core!</h1>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Run Development Mode
|
||||
|
||||
```bash
|
||||
wails3 dev
|
||||
```
|
||||
|
||||
## Build for Production
|
||||
|
||||
```bash
|
||||
wails3 build
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Architecture](architecture.md) - Understand how Core works
|
||||
- [Display Service](../services/display.md) - Window and dialog management
|
||||
- [MCP Integration](../services/mcp.md) - AI tool support
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
---
|
||||
title: runtime
|
||||
---
|
||||
# Service: `runtime`
|
||||
|
||||
The `runtime` service provides the main entry point for the application and a helper structure for services to interact with the `Core`.
|
||||
|
||||
## Types
|
||||
|
||||
### `type Runtime`
|
||||
|
||||
`Runtime` is the top-level container that holds the `Core` instance and the Wails application. It serves as the bridge between Wails and the Core framework.
|
||||
|
||||
```go
|
||||
type Runtime struct {
|
||||
// Core is the central service manager
|
||||
Core *Core
|
||||
// app is the Wails application instance
|
||||
app *application.App
|
||||
}
|
||||
```
|
||||
|
||||
### `type ServiceRuntime[T any]`
|
||||
|
||||
`ServiceRuntime` is a generic helper struct designed to be embedded in service implementations. It provides easy access to the `Core` and service-specific options.
|
||||
|
||||
```go
|
||||
type ServiceRuntime[T any] struct {
|
||||
core *Core
|
||||
opts T
|
||||
}
|
||||
```
|
||||
|
||||
### `type ServiceFactory`
|
||||
|
||||
`ServiceFactory` is a function type that creates a service instance.
|
||||
|
||||
```go
|
||||
type ServiceFactory func() (any, error)
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### `func NewRuntime(app *application.App) (*Runtime, error)`
|
||||
|
||||
`NewRuntime` creates and wires together all application services using default settings. It is the standard way to initialize the runtime.
|
||||
|
||||
### `func NewWithFactories(app *application.App, factories map[string]ServiceFactory) (*Runtime, error)`
|
||||
|
||||
`NewWithFactories` creates a new `Runtime` instance using a provided map of service factories. This allows for flexible, dynamic service registration.
|
||||
|
||||
### `func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T]`
|
||||
|
||||
`NewServiceRuntime` creates a new `ServiceRuntime` instance. This is typically used in a service's factory or constructor.
|
||||
|
||||
## Methods
|
||||
|
||||
### `func (r *Runtime) ServiceName() string`
|
||||
|
||||
`ServiceName` returns the name of the service ("Core"). This is used by Wails for service identification.
|
||||
|
||||
### `func (r *Runtime) ServiceStartup(ctx context.Context, options application.ServiceOptions)`
|
||||
|
||||
`ServiceStartup` delegates the startup lifecycle event to the underlying `Core`, which in turn initializes all registered services.
|
||||
|
||||
### `func (r *Runtime) ServiceShutdown(ctx context.Context)`
|
||||
|
||||
`ServiceShutdown` delegates the shutdown lifecycle event to the underlying `Core`.
|
||||
|
||||
### `func (r *ServiceRuntime[T]) Core() *Core`
|
||||
|
||||
`Core` returns the central `Core` instance, giving the service access to other services and features.
|
||||
|
||||
### `func (r *ServiceRuntime[T]) Config() Config`
|
||||
|
||||
`Config` returns the registered `Config` service from the `Core`. It is a convenience method for accessing configuration.
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
# Services
|
||||
|
||||
Services are the building blocks of a Core application. Each service encapsulates a specific domain of functionality.
|
||||
|
||||
## Creating a Service
|
||||
|
||||
```go
|
||||
package myservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Snider/Core/pkg/core"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
core *core.Core
|
||||
}
|
||||
|
||||
// Factory function for registration
|
||||
func NewService(c *core.Core) (any, error) {
|
||||
return &Service{core: c}, nil
|
||||
}
|
||||
|
||||
// Implement Startable for startup logic
|
||||
func (s *Service) OnStartup(ctx context.Context) error {
|
||||
// Initialize resources
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implement Stoppable for cleanup
|
||||
func (s *Service) OnShutdown(ctx context.Context) error {
|
||||
// Release resources
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Registering Services
|
||||
|
||||
```go
|
||||
c, err := core.New(
|
||||
// Auto-discover name from package path
|
||||
core.WithService(myservice.NewService),
|
||||
|
||||
// Explicit name
|
||||
core.WithName("custom", func(c *core.Core) (any, error) {
|
||||
return &CustomService{}, nil
|
||||
}),
|
||||
)
|
||||
```
|
||||
|
||||
## Retrieving Services
|
||||
|
||||
```go
|
||||
// Safe retrieval with error
|
||||
svc, err := core.ServiceFor[*myservice.Service](c, "myservice")
|
||||
if err != nil {
|
||||
log.Printf("Service not found: %v", err)
|
||||
}
|
||||
|
||||
// Must retrieval (panics if not found)
|
||||
svc := core.MustServiceFor[*myservice.Service](c, "myservice")
|
||||
```
|
||||
|
||||
## Service Dependencies
|
||||
|
||||
Services can depend on other services:
|
||||
|
||||
```go
|
||||
func NewOrderService(c *core.Core) (any, error) {
|
||||
// Get required dependencies
|
||||
userSvc := core.MustServiceFor[*user.Service](c, "user")
|
||||
paymentSvc := core.MustServiceFor[*payment.Service](c, "payment")
|
||||
|
||||
return &OrderService{
|
||||
users: userSvc,
|
||||
payments: paymentSvc,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning "Dependency Order"
|
||||
Register dependencies before services that use them. Core does not automatically resolve dependency order.
|
||||
|
||||
## Exposing to Frontend
|
||||
|
||||
Services are automatically exposed to the frontend via Wails bindings:
|
||||
|
||||
```go
|
||||
// Go service method
|
||||
func (s *Service) GetUser(id string) (*User, error) {
|
||||
return s.db.FindUser(id)
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// TypeScript (auto-generated)
|
||||
import { GetUser } from '@bindings/myservice/service';
|
||||
|
||||
const user = await GetUser("123");
|
||||
```
|
||||
|
||||
## Testing Services
|
||||
|
||||
```go
|
||||
func TestMyService(t *testing.T) {
|
||||
// Create mock core
|
||||
c, _ := core.New()
|
||||
|
||||
// Create service
|
||||
svc, err := NewService(c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test methods
|
||||
result := svc.(*Service).DoSomething()
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
```
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
# WebView Service
|
||||
|
||||
The WebView service (`pkg/webview`) provides programmatic interaction with web content in your application windows.
|
||||
|
||||
## Features
|
||||
|
||||
- JavaScript execution
|
||||
- DOM manipulation
|
||||
- Element interaction (click, type, select)
|
||||
- Console message capture
|
||||
- Screenshots
|
||||
- Network request monitoring
|
||||
- Performance metrics
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/webview"
|
||||
|
||||
// Create service
|
||||
wv := webview.New()
|
||||
|
||||
// Set Wails app reference
|
||||
wv.SetApp(app)
|
||||
```
|
||||
|
||||
## JavaScript Execution
|
||||
|
||||
```go
|
||||
// Execute JavaScript and get result
|
||||
result, err := wv.ExecJS("main", `
|
||||
document.title
|
||||
`)
|
||||
|
||||
// Execute complex scripts
|
||||
result, err := wv.ExecJS("main", `
|
||||
const items = document.querySelectorAll('.item');
|
||||
Array.from(items).map(el => el.textContent);
|
||||
`)
|
||||
```
|
||||
|
||||
## DOM Interaction
|
||||
|
||||
### Click Element
|
||||
|
||||
```go
|
||||
err := wv.Click("main", "#submit-button")
|
||||
err := wv.Click("main", ".nav-link:first-child")
|
||||
```
|
||||
|
||||
### Type Text
|
||||
|
||||
```go
|
||||
err := wv.Type("main", "#search-input", "hello world")
|
||||
```
|
||||
|
||||
### Select Option
|
||||
|
||||
```go
|
||||
err := wv.Select("main", "#country-select", "US")
|
||||
```
|
||||
|
||||
### Check/Uncheck
|
||||
|
||||
```go
|
||||
err := wv.Check("main", "#agree-checkbox", true)
|
||||
```
|
||||
|
||||
### Hover
|
||||
|
||||
```go
|
||||
err := wv.Hover("main", ".dropdown-trigger")
|
||||
```
|
||||
|
||||
### Scroll
|
||||
|
||||
```go
|
||||
// Scroll to element
|
||||
err := wv.Scroll("main", "#section-3", 0, 0)
|
||||
|
||||
// Scroll by coordinates
|
||||
err := wv.Scroll("main", "", 0, 500)
|
||||
```
|
||||
|
||||
## Element Information
|
||||
|
||||
### Query Selector
|
||||
|
||||
```go
|
||||
elements, err := wv.QuerySelector("main", ".list-item")
|
||||
```
|
||||
|
||||
### Element Info
|
||||
|
||||
```go
|
||||
info, err := wv.GetElementInfo("main", "#user-card")
|
||||
// Returns: tag, id, classes, text, attributes, bounds
|
||||
```
|
||||
|
||||
### Computed Styles
|
||||
|
||||
```go
|
||||
styles, err := wv.GetComputedStyle("main", ".button",
|
||||
[]string{"color", "background-color", "font-size"})
|
||||
```
|
||||
|
||||
### DOM Tree
|
||||
|
||||
```go
|
||||
tree, err := wv.GetDOMTree("main", 5) // max depth 5
|
||||
```
|
||||
|
||||
## Console Messages
|
||||
|
||||
```go
|
||||
// Setup console listener
|
||||
wv.SetupConsoleListener()
|
||||
|
||||
// Inject capture script
|
||||
wv.InjectConsoleCapture("main")
|
||||
|
||||
// Get messages
|
||||
messages := wv.GetConsoleMessages("all", 100)
|
||||
messages := wv.GetConsoleMessages("error", 50)
|
||||
|
||||
// Clear buffer
|
||||
wv.ClearConsole()
|
||||
|
||||
// Get errors only
|
||||
errors := wv.GetErrors(50)
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
```go
|
||||
// Full page screenshot (base64 PNG)
|
||||
data, err := wv.Screenshot("main")
|
||||
|
||||
// Element screenshot
|
||||
data, err := wv.ScreenshotElement("main", "#chart")
|
||||
|
||||
// Export as PDF
|
||||
pdfData, err := wv.ExportToPDF("main", map[string]any{
|
||||
"margin": 20,
|
||||
})
|
||||
```
|
||||
|
||||
## Page Information
|
||||
|
||||
```go
|
||||
// Get current URL
|
||||
url, err := wv.GetURL("main")
|
||||
|
||||
// Get page title
|
||||
title, err := wv.GetTitle("main")
|
||||
|
||||
// Get page source
|
||||
source, err := wv.GetPageSource("main")
|
||||
|
||||
// Navigate
|
||||
err := wv.Navigate("main", "https://example.com")
|
||||
```
|
||||
|
||||
## Network Monitoring
|
||||
|
||||
```go
|
||||
// Inject network interceptor
|
||||
wv.InjectNetworkInterceptor("main")
|
||||
|
||||
// Get captured requests
|
||||
requests, err := wv.GetNetworkRequests("main", 100)
|
||||
|
||||
// Clear request log
|
||||
wv.ClearNetworkRequests("main")
|
||||
```
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
```go
|
||||
metrics, err := wv.GetPerformance("main")
|
||||
// Returns: loadTime, domContentLoaded, firstPaint, etc.
|
||||
```
|
||||
|
||||
## Resource Listing
|
||||
|
||||
```go
|
||||
resources, err := wv.GetResources("main")
|
||||
// Returns: scripts, stylesheets, images, fonts, etc.
|
||||
```
|
||||
|
||||
## Visual Debugging
|
||||
|
||||
```go
|
||||
// Highlight element temporarily
|
||||
err := wv.Highlight("main", "#target-element", 2000) // 2 seconds
|
||||
```
|
||||
|
||||
## Window Listing
|
||||
|
||||
```go
|
||||
windows := wv.ListWindows()
|
||||
for _, w := range windows {
|
||||
fmt.Println(w.Name)
|
||||
}
|
||||
```
|
||||
|
||||
## Frontend Usage
|
||||
|
||||
The WebView service is primarily used server-side for:
|
||||
|
||||
- Automated testing
|
||||
- AI assistant interactions (via MCP)
|
||||
- Scripted UI interactions
|
||||
|
||||
For normal frontend development, use standard DOM APIs directly.
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
# Workspace Service
|
||||
|
||||
The Workspace service (`pkg/workspace`) manages isolated user workspaces with encrypted storage and PGP key pairs.
|
||||
|
||||
## Features
|
||||
|
||||
- Isolated workspace environments
|
||||
- PGP key pair generation per workspace
|
||||
- Encrypted workspace identification
|
||||
- File operations within workspace context
|
||||
- Multiple workspace support
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
import "github.com/Snider/Core/pkg/workspace"
|
||||
|
||||
// With IO medium (standalone)
|
||||
medium, _ := local.New("/app/workspaces")
|
||||
ws, err := workspace.New(medium)
|
||||
|
||||
// With Core framework (recommended)
|
||||
c, _ := core.New(
|
||||
core.WithService(workspace.Register),
|
||||
)
|
||||
ws := core.MustServiceFor[*workspace.Service](c, "workspace")
|
||||
```
|
||||
|
||||
## Creating Workspaces
|
||||
|
||||
```go
|
||||
// Create a new encrypted workspace
|
||||
workspaceID, err := ws.CreateWorkspace("my-project", "secure-password")
|
||||
// Returns obfuscated workspace ID
|
||||
|
||||
// Workspace structure created:
|
||||
// workspaces/
|
||||
// <workspace-id>/
|
||||
// config/
|
||||
// log/
|
||||
// data/
|
||||
// files/
|
||||
// keys/
|
||||
// key.pub (PGP public key)
|
||||
// key.priv (PGP private key)
|
||||
```
|
||||
|
||||
## Switching Workspaces
|
||||
|
||||
```go
|
||||
// Switch to a workspace
|
||||
err := ws.SwitchWorkspace(workspaceID)
|
||||
|
||||
// Switch to default workspace
|
||||
err := ws.SwitchWorkspace("default")
|
||||
```
|
||||
|
||||
## Workspace File Operations
|
||||
|
||||
```go
|
||||
// Write file to active workspace
|
||||
err := ws.WorkspaceFileSet("config/settings.json", jsonData)
|
||||
|
||||
// Read file from active workspace
|
||||
content, err := ws.WorkspaceFileGet("config/settings.json")
|
||||
```
|
||||
|
||||
## Listing Workspaces
|
||||
|
||||
```go
|
||||
// Get all workspace IDs
|
||||
workspaces := ws.ListWorkspaces()
|
||||
for _, id := range workspaces {
|
||||
fmt.Println(id)
|
||||
}
|
||||
```
|
||||
|
||||
## Active Workspace
|
||||
|
||||
```go
|
||||
// Get current workspace info
|
||||
active := ws.ActiveWorkspace()
|
||||
if active != nil {
|
||||
fmt.Println("Name:", active.Name)
|
||||
fmt.Println("Path:", active.Path)
|
||||
}
|
||||
```
|
||||
|
||||
## Workspace Structure
|
||||
|
||||
Each workspace contains:
|
||||
|
||||
| Directory | Purpose |
|
||||
|-----------|---------|
|
||||
| `config/` | Workspace configuration files |
|
||||
| `log/` | Workspace logs |
|
||||
| `data/` | Application data |
|
||||
| `files/` | User files |
|
||||
| `keys/` | PGP key pair |
|
||||
|
||||
## Security Model
|
||||
|
||||
Workspaces use a two-level hashing scheme:
|
||||
|
||||
1. **Real Name**: Hash of the identifier
|
||||
2. **Workspace ID**: Hash of `workspace/{real_name}`
|
||||
|
||||
This prevents workspace enumeration while allowing consistent access.
|
||||
|
||||
## IPC Events
|
||||
|
||||
The workspace service responds to IPC messages:
|
||||
|
||||
```go
|
||||
// Switch workspace via IPC
|
||||
c.ACTION(core.Message{
|
||||
Type: "workspace.switch_workspace",
|
||||
Data: map[string]any{
|
||||
"name": workspaceID,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Frontend Usage (TypeScript)
|
||||
|
||||
```typescript
|
||||
import {
|
||||
CreateWorkspace,
|
||||
SwitchWorkspace,
|
||||
WorkspaceFileGet,
|
||||
WorkspaceFileSet,
|
||||
ListWorkspaces,
|
||||
ActiveWorkspace
|
||||
} from '@bindings/workspace/service';
|
||||
|
||||
// Create workspace
|
||||
const wsId = await CreateWorkspace("my-project", "password");
|
||||
|
||||
// Switch workspace
|
||||
await SwitchWorkspace(wsId);
|
||||
|
||||
// Read/write files
|
||||
const config = await WorkspaceFileGet("config/app.json");
|
||||
await WorkspaceFileSet("config/app.json", JSON.stringify(newConfig));
|
||||
|
||||
// List all workspaces
|
||||
const workspaces = await ListWorkspaces();
|
||||
|
||||
// Get active workspace
|
||||
const active = await ActiveWorkspace();
|
||||
console.log(`Current: ${active.Name} at ${active.Path}`);
|
||||
```
|
||||
Loading…
Add table
Reference in a new issue