ba919305c2
Features: - Unified Go binary running Task Service, Agent Manager, and Orchestrator - SQLite persistence for boards, columns, and tasks - Docker-based agent execution with ACP message streaming - Augment Agent integration with session resumption support - RESTful API for all operations (port 8080) - In-memory event bus for service communication Architecture: - cmd/kandev: Single entry point for all services - internal/task: Task, board, and column management - internal/agent: Docker container lifecycle and ACP streaming - internal/orchestrator: Agent coordination and monitoring - internal/events: In-memory pub/sub event bus Documentation: - README.md: Project overview and quick start - docs/ARCHITECTURE.md: System architecture (current + planned) - docs/GETTING_STARTED.md: Development setup guide - docs/IMPLEMENTATION_PLAN.md: Original microservices plan - backend/NEXT_STEPS.md: Development roadmap
115 lines
2.3 KiB
Go
115 lines
2.3 KiB
Go
package credentials
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
// FileProvider provides credentials from a JSON file
|
|
type FileProvider struct {
|
|
path string
|
|
credentials map[string]*Credential
|
|
mu sync.RWMutex
|
|
loaded bool
|
|
}
|
|
|
|
// NewFileProvider creates a new file provider
|
|
func NewFileProvider(path string) *FileProvider {
|
|
return &FileProvider{
|
|
path: path,
|
|
credentials: make(map[string]*Credential),
|
|
}
|
|
}
|
|
|
|
// Name returns the provider name
|
|
func (p *FileProvider) Name() string {
|
|
return "file"
|
|
}
|
|
|
|
// load loads credentials from the JSON file
|
|
// File format: {"ANTHROPIC_API_KEY": "sk-...", ...}
|
|
func (p *FileProvider) load() error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.loaded {
|
|
return nil
|
|
}
|
|
|
|
data, err := os.ReadFile(p.path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
// File doesn't exist, not an error - just no credentials
|
|
p.loaded = true
|
|
return nil
|
|
}
|
|
return fmt.Errorf("failed to read credentials file: %w", err)
|
|
}
|
|
|
|
// Parse JSON file
|
|
var rawCredentials map[string]string
|
|
if err := json.Unmarshal(data, &rawCredentials); err != nil {
|
|
return fmt.Errorf("failed to parse credentials file: %w", err)
|
|
}
|
|
|
|
// Convert to Credential objects
|
|
for key, value := range rawCredentials {
|
|
p.credentials[key] = &Credential{
|
|
Key: key,
|
|
Value: value,
|
|
Source: "file",
|
|
}
|
|
}
|
|
|
|
p.loaded = true
|
|
return nil
|
|
}
|
|
|
|
// GetCredential retrieves a credential from the file
|
|
func (p *FileProvider) GetCredential(ctx context.Context, key string) (*Credential, error) {
|
|
if err := p.load(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
cred, ok := p.credentials[key]
|
|
if !ok {
|
|
return nil, fmt.Errorf("credential not found: %s", key)
|
|
}
|
|
|
|
return cred, nil
|
|
}
|
|
|
|
// ListAvailable returns list of available credential keys from the file
|
|
func (p *FileProvider) ListAvailable(ctx context.Context) ([]string, error) {
|
|
if err := p.load(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
keys := make([]string, 0, len(p.credentials))
|
|
for key := range p.credentials {
|
|
keys = append(keys, key)
|
|
}
|
|
|
|
return keys, nil
|
|
}
|
|
|
|
// Reload forces a reload of credentials from the file
|
|
func (p *FileProvider) Reload() error {
|
|
p.mu.Lock()
|
|
p.loaded = false
|
|
p.credentials = make(map[string]*Credential)
|
|
p.mu.Unlock()
|
|
|
|
return p.load()
|
|
}
|
|
|