service

package
v0.19.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 12, 2026 License: MIT Imports: 47 Imported by: 0

README

Service Layer

The service layer provides high-level business logic and orchestrates operations across repositories, backends, and providers. It implements the core functionality of UniRTM while maintaining clean separation from infrastructure concerns.

Overview

The service layer sits between the CLI/API layer and the repository/backend layers. It:

  • Implements business logic and workflows
  • Orchestrates operations across multiple repositories
  • Provides transaction management
  • Handles error classification and logging
  • Enforces business rules and validation

Architecture

CLI/API Layer
     ↓
Service Layer (Business Logic)
     ↓
Repository Layer (Data Access)
     ↓
Database Layer (SQLite)

Components

Audit Service

The AuditService provides comprehensive audit logging functionality for all system operations.

Features:

  • High-level API for creating audit log entries
  • Convenience methods for common operations (install, uninstall, activate, etc.)
  • Integration with the logger for immediate visibility
  • Support for custom metadata (JSON-encoded)
  • Query capabilities with filters (time range, operation type, tool, status)

Usage Example:

// Initialize database and repository
db, err := database.Open(ctx, database.Config{
    Path:    "/path/to/unirtm.db",
    WALMode: true,
})
if err != nil {
    return err
}
defer db.Close()

auditRepo, err := sqlite.NewAuditRepository(db.Conn())
if err != nil {
    return err
}
defer auditRepo.Close()

// Create audit service
auditService := service.NewAuditService(auditRepo)

// Log a tool installation
err = auditService.LogInstall(ctx, "node", "20.0.0", 2*time.Second, nil)
if err != nil {
    return err
}

// Log a failed operation
installErr := fmt.Errorf("download failed: connection timeout")
err = auditService.LogInstall(ctx, "python", "3.11.0", 5*time.Second, installErr)
if err != nil {
    return err
}

// Query recent logs
logs, err := auditService.GetRecentLogs(ctx, 10)
if err != nil {
    return err
}

// Query logs by operation type
installLogs, err := auditService.GetLogsByOperation(ctx, service.OperationInstall, 20)
if err != nil {
    return err
}

// Query logs by tool
nodeLogs, err := auditService.GetLogsByTool(ctx, "node", 15)
if err != nil {
    return err
}

// Query failed operations
failedLogs, err := auditService.GetLogsByStatus(ctx, service.StatusFailure, 25)
if err != nil {
    return err
}

Custom Metadata:

entry := &service.AuditLogEntry{
    Operation: service.OperationInstall,
    Tool:      "node",
    Version:   "20.0.0",
    Status:    service.StatusSuccess,
    Duration:  2500,
    Metadata: map[string]interface{}{
        "backend":      "github",
        "download_url": "https://github.com/nodejs/node/releases/download/v20.0.0/node-v20.0.0.tar.gz",
        "size_bytes":   45678901,
        "checksum":     "abc123def456",
    },
}

err := auditService.LogOperation(ctx, entry)

Operation Types:

  • OperationInstall - Tool installation
  • OperationUninstall - Tool uninstallation
  • OperationActivate - Tool activation
  • OperationDeactivate - Tool deactivation
  • OperationUpdate - Tool update
  • OperationCachePurge - Cache purge
  • OperationConfigLoad - Configuration load
  • OperationConfigUpdate - Configuration update
  • OperationIndexUpdate - Index update
  • OperationVersionResolve - Version resolution

Operation Status:

  • StatusSuccess - Operation completed successfully
  • StatusFailure - Operation failed
Auto-Activation Manager

The AutoActivationManager provides automatic environment switching based on directory context.

Features:

  • Automatic detection of project configuration files
  • Shell hook generation for directory change events
  • Seamless environment switching when entering/leaving projects
  • Support for nested project configurations
  • Integration with shell initialization scripts

Usage Example:

// Create auto-activation manager
manager := service.NewAutoActivationManager(
    "/usr/local/unirtm/shims",
    "/var/lib/unirtm",
    configManager,
)

// Generate shell hook for bash
hook, err := manager.GenerateShellHook(ctx, service.ShellBash)
if err != nil {
    return err
}

// Add to shell initialization
fmt.Println("Add this to your ~/.bashrc:")
fmt.Println(hook.Content)
Index Manager

The IndexManager manages tool index storage, retrieval, search, and updates.

Features:

  • Tool index storage and retrieval
  • Search functionality (name, description, tags)
  • Filtering by backend type
  • Incremental index updates from multiple sources
  • Stale index detection and prompting
  • Offline operation support

Usage Example:

// Create index manager
indexManager, err := service.NewIndexManager(
    indexRepo,
    auditRepo,
    backends,
    service.IndexManagerConfig{
        StaleTimeout: 7 * 24 * time.Hour,
    },
)
if err != nil {
    return err
}

// Add a tool to the index
err = indexManager.UpsertTool(ctx, "node", "Node.js runtime",
    "https://nodejs.org", "MIT", "github", &service.ToolMetadata{
        AvailableVersions: []string{"20.0.0", "18.0.0"},
        Tags:              []string{"runtime", "javascript"},
    })

// Search for tools
results, err := indexManager.SearchTools(ctx, service.SearchOptions{
    Query: "javascript",
})

// Check if index is stale
shouldPrompt, message, err := indexManager.PromptForUpdate(ctx)
if shouldPrompt {
    fmt.Println(message)
}

// Filter by backend
githubTools, err := indexManager.FilterByBackend(ctx, "github")

Validates Requirements:

  • 11.1: Maintain searchable index
  • 11.2: Update from multiple sources
  • 11.3: Store tool metadata
  • 11.4: Search by name, description, tags
  • 11.5: Filter by backend type
  • 11.6: Incremental index updates
  • 11.7: Stale detection and prompting
  • 11.8: Offline operation support

Design Principles

Features:

  • Shell-specific script generation (bash, zsh, fish, PowerShell)
  • PATH modification to include shims directory
  • Environment variable setting for active tool versions
  • Project-specific activation support
  • Global activation support
  • Automatic shell detection

Usage Example:

// Create activation manager
manager := service.NewActivationManager("/usr/local/unirtm/shims", "/var/lib/unirtm")
ctx := context.Background()

// Generate global activation for bash
toolVersions := map[string]string{
    "node":   "20.0.0",
    "python": "3.11.0",
    "go":     "1.21.0",
}

script, err := manager.GenerateGlobalActivation(ctx, service.ShellBash, toolVersions)
if err != nil {
    return err
}

// Write script to file
err = os.WriteFile("activate.sh", []byte(script.Content), 0644)
if err != nil {
    return err
}

// Display instructions
fmt.Println(script.Instructions)

// Generate project-specific activation
envVars := map[string]string{
    "NODE_ENV": "development",
    "DEBUG":    "app:*",
}

projectScript, err := manager.GenerateProjectActivation(
    ctx,
    service.ShellBash,
    "/home/user/myproject",
    map[string]string{"node": "18.0.0"},
    envVars,
)
if err != nil {
    return err
}

// Detect current shell
shell, err := service.DetectShell()
if err != nil {
    return err
}

// Generate activation for detected shell
autoScript, err := manager.GenerateGlobalActivation(ctx, shell, toolVersions)
if err != nil {
    return err
}

Shell Types:

  • ShellBash - Bash shell
  • ShellZsh - Zsh shell
  • ShellFish - Fish shell
  • ShellPowerShell - PowerShell

Activation Scopes:

  • ScopeGlobal - System-wide default tool versions
  • ScopeProject - Project-specific tool versions

Core Design Principles

1. Single Responsibility

Each service has a single, well-defined purpose. The AuditService is responsible only for audit logging, not for performing the operations being audited.

2. Dependency Inversion

Services depend on repository interfaces, not concrete implementations. This allows for easy testing and swapping of implementations.

type AuditService struct {
    repo repository.AuditRepository  // Interface, not concrete type
}
3. Error Handling

Services use the error handling system from internal/pkg/errors to classify errors:

  • User Errors: Invalid input, configuration errors
  • System Errors: Database failures, disk full
  • External Errors: Network failures, backend API errors
4. Logging Integration

Services integrate with the logger from internal/pkg/logger to provide immediate visibility into operations:

logger.Info("Operation completed", map[string]interface{}{
    "operation": entry.Operation,
    "tool":      entry.Tool,
    "version":   entry.Version,
    "status":    entry.Status,
    "duration":  entry.Duration,
})
5. Context Propagation

All service methods accept context.Context as the first parameter for:

  • Cancellation support
  • Timeout enforcement
  • Request-scoped values (request ID, user ID, etc.)
6. Transaction Support

Services that perform multiple database operations should use the transaction manager from internal/transaction to ensure atomicity.

Testing

Unit Tests

Unit tests use mock repositories to test business logic in isolation:

type MockAuditRepository struct {
    logFunc   func(ctx context.Context, entry *repository.AuditEntry) error
    queryFunc func(ctx context.Context, filter repository.AuditFilter) ([]*repository.AuditEntry, error)
}

func TestAuditService_LogInstall(t *testing.T) {
    repo := &MockAuditRepository{
        logFunc: func(ctx context.Context, entry *repository.AuditEntry) error {
            assert.Equal(t, "install", entry.Operation)
            assert.Equal(t, "node", entry.Tool)
            return nil
        },
    }

    service := NewAuditService(repo)
    err := service.LogInstall(ctx, "node", "20.0.0", 2*time.Second, nil)
    require.NoError(t, err)
}
Integration Tests

Integration tests use real database connections to test the full stack:

func TestAuditService_Integration(t *testing.T) {
    db, err := database.Open(ctx, database.Config{
        Path:    ":memory:",
        WALMode: false,
    })
    require.NoError(t, err)
    defer db.Close()

    auditRepo, err := sqlite.NewAuditRepository(db.Conn())
    require.NoError(t, err)
    defer auditRepo.Close()

    service := NewAuditService(auditRepo)

    // Test full workflow
    err = service.LogInstall(ctx, "node", "20.0.0", 2*time.Second, nil)
    require.NoError(t, err)

    logs, err := service.GetRecentLogs(ctx, 10)
    require.NoError(t, err)
    assert.Len(t, logs, 1)
}

Future Services

The service layer will be expanded to include:

  • InstallationService: Tool installation and management ✅ (Implemented)
  • ConfigService: Configuration management and validation
  • CacheService: Cache management and purging ✅ (Implemented)
  • IndexService: Tool index management and search ✅ (Implemented)
  • VersionService: Version resolution and management ✅ (Implemented)
  • BackendService: Backend coordination and selection
  • ProviderService: Provider management and delegation
  • ActivationService: Environment activation management ✅ (Implemented)
  • AutoActivationService: Automatic environment switching ✅ (Implemented)

Requirements Validation

The audit service validates the following requirements:

  • Requirement 7.8: Audit logging to database
  • Requirement 8.1: Log all operations to audit log before execution
  • Requirement 8.5: Record operation type, timestamp, user, affected tools, success/failure status, error messages

References

Documentation

Overview

Package service provides high-level business logic for UniRTM operations.

Package service provides high-level business logic for UniRTM operations.

Package service provides business logic for UniRTM operations.

Package service provides business logic for UniRTM operations.

Package service provides business logic for UniRTM operations.

Package service provides business logic for UniRTM operations.

Package service provides business logic for UniRTM operations.

Package service provides the plugin loading and management capabilities. It relies on HashiCorp go-plugin to dynamically load standalone binaries that implement the Backend or Provider interfaces via RPC.

Package service provides business logic for UniRTM operations.

Package service provides business logic for UniRTM operations.

Package service provides business logic for UniRTM operations.

Shim scripts are thin wrapper scripts placed in the shims directory that intercept calls to tools and delegate to the correct version based on the active environment settings.

Validates Requirements: 14.1, 14.2, 14.3, 14.4, 14.5, 14.6, 14.7

Package service provides business logic for UniRTM operations.

Example (VersionManager_basicUsage)

Example_versionManager_basicUsage demonstrates basic Version Manager usage

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/backend"
	"github.com/snowdreamtech/unirtm/internal/service"
)

// mockBackendForExample is a simple mock backend for examples
type mockBackendForExample struct {
	name string
}

func (m *mockBackendForExample) Name() string {
	return m.name
}

func (m *mockBackendForExample) AttestationType() string {
	return ""
}

func (m *mockBackendForExample) Dependencies() []string {
	return nil
}

func (m *mockBackendForExample) GetReach() string {
	return ""
}

func (m *mockBackendForExample) IsRecommended() bool {
	return false
}

func (m *mockBackendForExample) IsScriptless() bool {
	return false
}

func (m *mockBackendForExample) IsStable() bool {
	return true
}

func (m *mockBackendForExample) SupportsOffline() bool {
	return false
}

func (m *mockBackendForExample) ListVersions(ctx context.Context, tool string, platform backend.Platform) ([]backend.VersionInfo, error) {

	return []backend.VersionInfo{
		{Version: "20.11.0", DownloadURL: "https://example.com/node-20.11.0.tar.gz"},
		{Version: "20.10.0", DownloadURL: "https://example.com/node-20.10.0.tar.gz"},
		{Version: "18.19.0", DownloadURL: "https://example.com/node-18.19.0.tar.gz"},
	}, nil
}

func (m *mockBackendForExample) ResolveVersion(ctx context.Context, tool string, versionRequest string, platform backend.Platform) (*backend.VersionInfo, error) {

	if versionRequest == "latest" {
		return &backend.VersionInfo{
			Version:     "20.11.0",
			DownloadURL: "https://example.com/node-20.11.0.tar.gz",
			Checksum:    "abc123def456",
			Platform:    platform,
		}, nil
	}

	if versionRequest == "^20.0.0" {
		return &backend.VersionInfo{
			Version:     "20.11.0",
			DownloadURL: "https://example.com/node-20.11.0.tar.gz",
			Checksum:    "abc123def456",
			Platform:    platform,
		}, nil
	}

	return nil, fmt.Errorf("unsupported version request: %s", versionRequest)
}

func (m *mockBackendForExample) GetDownloadInfo(ctx context.Context, tool string, version string, platform backend.Platform) (*backend.VersionInfo, error) {

	return &backend.VersionInfo{
		Version:     version,
		DownloadURL: fmt.Sprintf("https://example.com/%s-%s.tar.gz", tool, version),
		Checksum:    "abc123def456",
		Platform:    platform,
	}, nil
}

func (m *mockBackendForExample) SupportsChecksum() bool {
	return true
}

func (m *mockBackendForExample) SupportsGPG() bool {
	return false
}

func main() {
	// Create backends
	backends := map[string]backend.Backend{
		"github": &mockBackendForExample{name: "github"},
	}

	// Create Version Manager
	vm := service.NewVersionManager(backends)

	// Resolve exact version
	ctx := context.Background()
	platform := backend.Platform{OS: "linux", Arch: "amd64"}

	versionInfo, err := vm.ResolveVersion(ctx, "github", "node", "20.0.0", platform)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Printf("Version: %s\n", versionInfo.Version)
	fmt.Printf("Download URL: %s\n", versionInfo.DownloadURL)

}
Output:
Version: 20.0.0
Download URL: https://example.com/node-20.0.0.tar.gz
Example (VersionManager_explicitRequirement)

Example_versionManager_explicitRequirement demonstrates explicit version requirement enforcement

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/backend"
	"github.com/snowdreamtech/unirtm/internal/service"
)

// mockBackendForExample is a simple mock backend for examples
type mockBackendForExample struct {
	name string
}

func (m *mockBackendForExample) Name() string {
	return m.name
}

func (m *mockBackendForExample) AttestationType() string {
	return ""
}

func (m *mockBackendForExample) Dependencies() []string {
	return nil
}

func (m *mockBackendForExample) GetReach() string {
	return ""
}

func (m *mockBackendForExample) IsRecommended() bool {
	return false
}

func (m *mockBackendForExample) IsScriptless() bool {
	return false
}

func (m *mockBackendForExample) IsStable() bool {
	return true
}

func (m *mockBackendForExample) SupportsOffline() bool {
	return false
}

func (m *mockBackendForExample) ListVersions(ctx context.Context, tool string, platform backend.Platform) ([]backend.VersionInfo, error) {

	return []backend.VersionInfo{
		{Version: "20.11.0", DownloadURL: "https://example.com/node-20.11.0.tar.gz"},
		{Version: "20.10.0", DownloadURL: "https://example.com/node-20.10.0.tar.gz"},
		{Version: "18.19.0", DownloadURL: "https://example.com/node-18.19.0.tar.gz"},
	}, nil
}

func (m *mockBackendForExample) ResolveVersion(ctx context.Context, tool string, versionRequest string, platform backend.Platform) (*backend.VersionInfo, error) {

	if versionRequest == "latest" {
		return &backend.VersionInfo{
			Version:     "20.11.0",
			DownloadURL: "https://example.com/node-20.11.0.tar.gz",
			Checksum:    "abc123def456",
			Platform:    platform,
		}, nil
	}

	if versionRequest == "^20.0.0" {
		return &backend.VersionInfo{
			Version:     "20.11.0",
			DownloadURL: "https://example.com/node-20.11.0.tar.gz",
			Checksum:    "abc123def456",
			Platform:    platform,
		}, nil
	}

	return nil, fmt.Errorf("unsupported version request: %s", versionRequest)
}

func (m *mockBackendForExample) GetDownloadInfo(ctx context.Context, tool string, version string, platform backend.Platform) (*backend.VersionInfo, error) {

	return &backend.VersionInfo{
		Version:     version,
		DownloadURL: fmt.Sprintf("https://example.com/%s-%s.tar.gz", tool, version),
		Checksum:    "abc123def456",
		Platform:    platform,
	}, nil
}

func (m *mockBackendForExample) SupportsChecksum() bool {
	return true
}

func (m *mockBackendForExample) SupportsGPG() bool {
	return false
}

func main() {
	backends := map[string]backend.Backend{
		"github": &mockBackendForExample{name: "github"},
	}

	vm := service.NewVersionManager(backends)

	ctx := context.Background()
	platform := backend.Platform{OS: "linux", Arch: "amd64"}

	// Try to resolve without specifying a version
	_, err := vm.ResolveVersion(ctx, "github", "node", "", platform)
	if err != nil {
		fmt.Println("Error:", err)
	}

}
Output:
Error: explicit version specification required for tool 'node': must specify an exact version (e.g., 1.20.0), range (e.g., >=1.20.0, ^3.11, ~2.7.0), or alias (latest, lts, stable)
Example (VersionManager_listVersions)

Example_versionManager_listVersions demonstrates listing available versions

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/backend"
	"github.com/snowdreamtech/unirtm/internal/service"
)

// mockBackendForExample is a simple mock backend for examples
type mockBackendForExample struct {
	name string
}

func (m *mockBackendForExample) Name() string {
	return m.name
}

func (m *mockBackendForExample) AttestationType() string {
	return ""
}

func (m *mockBackendForExample) Dependencies() []string {
	return nil
}

func (m *mockBackendForExample) GetReach() string {
	return ""
}

func (m *mockBackendForExample) IsRecommended() bool {
	return false
}

func (m *mockBackendForExample) IsScriptless() bool {
	return false
}

func (m *mockBackendForExample) IsStable() bool {
	return true
}

func (m *mockBackendForExample) SupportsOffline() bool {
	return false
}

func (m *mockBackendForExample) ListVersions(ctx context.Context, tool string, platform backend.Platform) ([]backend.VersionInfo, error) {

	return []backend.VersionInfo{
		{Version: "20.11.0", DownloadURL: "https://example.com/node-20.11.0.tar.gz"},
		{Version: "20.10.0", DownloadURL: "https://example.com/node-20.10.0.tar.gz"},
		{Version: "18.19.0", DownloadURL: "https://example.com/node-18.19.0.tar.gz"},
	}, nil
}

func (m *mockBackendForExample) ResolveVersion(ctx context.Context, tool string, versionRequest string, platform backend.Platform) (*backend.VersionInfo, error) {

	if versionRequest == "latest" {
		return &backend.VersionInfo{
			Version:     "20.11.0",
			DownloadURL: "https://example.com/node-20.11.0.tar.gz",
			Checksum:    "abc123def456",
			Platform:    platform,
		}, nil
	}

	if versionRequest == "^20.0.0" {
		return &backend.VersionInfo{
			Version:     "20.11.0",
			DownloadURL: "https://example.com/node-20.11.0.tar.gz",
			Checksum:    "abc123def456",
			Platform:    platform,
		}, nil
	}

	return nil, fmt.Errorf("unsupported version request: %s", versionRequest)
}

func (m *mockBackendForExample) GetDownloadInfo(ctx context.Context, tool string, version string, platform backend.Platform) (*backend.VersionInfo, error) {

	return &backend.VersionInfo{
		Version:     version,
		DownloadURL: fmt.Sprintf("https://example.com/%s-%s.tar.gz", tool, version),
		Checksum:    "abc123def456",
		Platform:    platform,
	}, nil
}

func (m *mockBackendForExample) SupportsChecksum() bool {
	return true
}

func (m *mockBackendForExample) SupportsGPG() bool {
	return false
}

func main() {
	backends := map[string]backend.Backend{
		"github": &mockBackendForExample{name: "github"},
	}

	vm := service.NewVersionManager(backends)

	ctx := context.Background()
	platform := backend.Platform{OS: "linux", Arch: "amd64"}

	// List available versions
	versions, err := vm.ListAvailableVersions(ctx, "github", "node", platform)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println("Available versions:")
	for _, v := range versions {
		fmt.Printf("  - %s\n", v.Version)
	}

}
Output:
Available versions:
  - 20.11.0
  - 20.10.0
  - 18.19.0
Example (VersionManager_resolveAlias)

Example_versionManager_resolveAlias demonstrates resolving version aliases

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/backend"
	"github.com/snowdreamtech/unirtm/internal/service"
)

// mockBackendForExample is a simple mock backend for examples
type mockBackendForExample struct {
	name string
}

func (m *mockBackendForExample) Name() string {
	return m.name
}

func (m *mockBackendForExample) AttestationType() string {
	return ""
}

func (m *mockBackendForExample) Dependencies() []string {
	return nil
}

func (m *mockBackendForExample) GetReach() string {
	return ""
}

func (m *mockBackendForExample) IsRecommended() bool {
	return false
}

func (m *mockBackendForExample) IsScriptless() bool {
	return false
}

func (m *mockBackendForExample) IsStable() bool {
	return true
}

func (m *mockBackendForExample) SupportsOffline() bool {
	return false
}

func (m *mockBackendForExample) ListVersions(ctx context.Context, tool string, platform backend.Platform) ([]backend.VersionInfo, error) {

	return []backend.VersionInfo{
		{Version: "20.11.0", DownloadURL: "https://example.com/node-20.11.0.tar.gz"},
		{Version: "20.10.0", DownloadURL: "https://example.com/node-20.10.0.tar.gz"},
		{Version: "18.19.0", DownloadURL: "https://example.com/node-18.19.0.tar.gz"},
	}, nil
}

func (m *mockBackendForExample) ResolveVersion(ctx context.Context, tool string, versionRequest string, platform backend.Platform) (*backend.VersionInfo, error) {

	if versionRequest == "latest" {
		return &backend.VersionInfo{
			Version:     "20.11.0",
			DownloadURL: "https://example.com/node-20.11.0.tar.gz",
			Checksum:    "abc123def456",
			Platform:    platform,
		}, nil
	}

	if versionRequest == "^20.0.0" {
		return &backend.VersionInfo{
			Version:     "20.11.0",
			DownloadURL: "https://example.com/node-20.11.0.tar.gz",
			Checksum:    "abc123def456",
			Platform:    platform,
		}, nil
	}

	return nil, fmt.Errorf("unsupported version request: %s", versionRequest)
}

func (m *mockBackendForExample) GetDownloadInfo(ctx context.Context, tool string, version string, platform backend.Platform) (*backend.VersionInfo, error) {

	return &backend.VersionInfo{
		Version:     version,
		DownloadURL: fmt.Sprintf("https://example.com/%s-%s.tar.gz", tool, version),
		Checksum:    "abc123def456",
		Platform:    platform,
	}, nil
}

func (m *mockBackendForExample) SupportsChecksum() bool {
	return true
}

func (m *mockBackendForExample) SupportsGPG() bool {
	return false
}

func main() {
	backends := map[string]backend.Backend{
		"github": &mockBackendForExample{name: "github"},
	}

	vm := service.NewVersionManager(backends)

	ctx := context.Background()
	platform := backend.Platform{OS: "linux", Arch: "amd64"}

	// Resolve "latest" alias
	versionInfo, err := vm.ResolveVersion(ctx, "github", "node", "latest", platform)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Printf("Latest version: %s\n", versionInfo.Version)

}
Output:
Latest version: 20.11.0
Example (VersionManager_resolveRange)

Example_versionManager_resolveRange demonstrates resolving version ranges

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/backend"
	"github.com/snowdreamtech/unirtm/internal/service"
)

// mockBackendForExample is a simple mock backend for examples
type mockBackendForExample struct {
	name string
}

func (m *mockBackendForExample) Name() string {
	return m.name
}

func (m *mockBackendForExample) AttestationType() string {
	return ""
}

func (m *mockBackendForExample) Dependencies() []string {
	return nil
}

func (m *mockBackendForExample) GetReach() string {
	return ""
}

func (m *mockBackendForExample) IsRecommended() bool {
	return false
}

func (m *mockBackendForExample) IsScriptless() bool {
	return false
}

func (m *mockBackendForExample) IsStable() bool {
	return true
}

func (m *mockBackendForExample) SupportsOffline() bool {
	return false
}

func (m *mockBackendForExample) ListVersions(ctx context.Context, tool string, platform backend.Platform) ([]backend.VersionInfo, error) {

	return []backend.VersionInfo{
		{Version: "20.11.0", DownloadURL: "https://example.com/node-20.11.0.tar.gz"},
		{Version: "20.10.0", DownloadURL: "https://example.com/node-20.10.0.tar.gz"},
		{Version: "18.19.0", DownloadURL: "https://example.com/node-18.19.0.tar.gz"},
	}, nil
}

func (m *mockBackendForExample) ResolveVersion(ctx context.Context, tool string, versionRequest string, platform backend.Platform) (*backend.VersionInfo, error) {

	if versionRequest == "latest" {
		return &backend.VersionInfo{
			Version:     "20.11.0",
			DownloadURL: "https://example.com/node-20.11.0.tar.gz",
			Checksum:    "abc123def456",
			Platform:    platform,
		}, nil
	}

	if versionRequest == "^20.0.0" {
		return &backend.VersionInfo{
			Version:     "20.11.0",
			DownloadURL: "https://example.com/node-20.11.0.tar.gz",
			Checksum:    "abc123def456",
			Platform:    platform,
		}, nil
	}

	return nil, fmt.Errorf("unsupported version request: %s", versionRequest)
}

func (m *mockBackendForExample) GetDownloadInfo(ctx context.Context, tool string, version string, platform backend.Platform) (*backend.VersionInfo, error) {

	return &backend.VersionInfo{
		Version:     version,
		DownloadURL: fmt.Sprintf("https://example.com/%s-%s.tar.gz", tool, version),
		Checksum:    "abc123def456",
		Platform:    platform,
	}, nil
}

func (m *mockBackendForExample) SupportsChecksum() bool {
	return true
}

func (m *mockBackendForExample) SupportsGPG() bool {
	return false
}

func main() {
	backends := map[string]backend.Backend{
		"github": &mockBackendForExample{name: "github"},
	}

	vm := service.NewVersionManager(backends)

	ctx := context.Background()
	platform := backend.Platform{OS: "linux", Arch: "amd64"}

	// Resolve caret range
	versionInfo, err := vm.ResolveVersion(ctx, "github", "node", "^20.0.0", platform)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Printf("Resolved ^20.0.0 to: %s\n", versionInfo.Version)

}
Output:
Resolved ^20.0.0 to: 20.11.0
Example (VersionManager_validateConstraint)

Example_versionManager_validateConstraint demonstrates version constraint validation

package main

import (
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	vm := service.NewVersionManager(nil)

	// Validate various version constraints
	constraints := []string{
		"20.0.0",
		"^20.0.0",
		">=18.0.0",
		"latest",
	}

	for _, constraint := range constraints {
		if err := vm.ValidateVersionConstraint(constraint); err != nil {
			fmt.Printf("Invalid: %s - %v\n", constraint, err)
		} else {
			fmt.Printf("Valid: %s\n", constraint)
		}
	}

}
Output:
Valid: 20.0.0
Valid: ^20.0.0
Valid: >=18.0.0
Valid: latest

Index

Examples

Constants

View Source
const (
	ContextKeyQuietProgress    = "quietProgress"
	ContextKeyProgressReporter = "concurrentProgressReporter"
)
View Source
const PluginAPIVersion = "v1"

PluginAPIVersion defines the current plugin API version for compatibility checking.

Validates Requirement: 22.3 (API stability and versioning)

Variables

View Source
var ErrAlreadyInstalled = fmt.Errorf("already installed")

ErrAlreadyInstalled is returned when a tool version is already installed.

Functions

func ExecuteBinary

func ExecuteBinary(binPath string, args []string) error

ExecuteBinary executes a binary with arguments, replacing the current process on Unix.

Types

type ActivationAction

type ActivationAction string

ActivationAction represents the type of activation change.

const (
	// ActionActivate indicates activating a new project environment
	ActionActivate ActivationAction = "activate"
	// ActionDeactivate indicates deactivating the current project environment
	ActionDeactivate ActivationAction = "deactivate"
	// ActionSwitch indicates switching from one project to another
	ActionSwitch ActivationAction = "switch"
	// ActionNone indicates no change is needed
	ActionNone ActivationAction = "none"
)

type ActivationChange

type ActivationChange struct {
	// Action is the type of change (activate, deactivate, switch)
	Action ActivationAction
	// Script is the shell script to execute the change
	Script string
	// NewState is the new environment state after the change
	NewState *EnvironmentState
}

ActivationChange represents the changes needed to update the environment.

type ActivationConfig

type ActivationConfig struct {
	// Shell is the target shell type
	Shell ShellType
	// Scope is the activation scope (global or project)
	Scope ActivationScope
	// ShimsDir is the directory containing shim scripts
	ShimsDir string
	// ProjectDir is the project directory (for project-specific activation)
	ProjectDir string
	// ToolVersions maps tool names to their active versions
	ToolVersions map[string]string
	// EnvVars contains additional environment variables to set
	EnvVars map[string]string
	// Sources contains shell scripts to source
	// ExePath is the absolute path to the UniRTM executable
	ExePath string
	Sources []string
	// UseShims indicates whether to use shims (true) or PATH mode (false)
	UseShims bool
	// InjectedPaths is the list of absolute paths to inject into PATH (if not using shims)
	InjectedPaths []string
}

ActivationConfig contains configuration for activation.

type ActivationManager

type ActivationManager struct {
	// contains filtered or unexported fields
}

ActivationManager manages environment activation for tools.

func NewActivationManager

func NewActivationManager(shimsDir, dataDir string, registry *provider.Registry) *ActivationManager

NewActivationManager creates a new ActivationManager.

func (*ActivationManager) GenerateActivationScript

func (m *ActivationManager) GenerateActivationScript(ctx context.Context, config ActivationConfig) (*ActivationScript, error)

GenerateActivationScript generates a shell-specific activation script.

The script modifies PATH to include the shims directory and sets environment variables for active tool versions. The script format depends on the target shell.

Requirements: 15.1, 15.2, 15.3

Example (Fish)

ExampleActivationManager_GenerateActivationScript_fish demonstrates generating an activation script for fish shell.

package main

import (
	"github.com/snowdreamtech/unirtm/internal/provider"

	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	manager := service.NewActivationManager("/usr/local/unirtm/shims", "/var/lib/unirtm", provider.NewRegistry())
	ctx := context.Background()

	config := service.ActivationConfig{
		Shell:    service.ShellFish,
		Scope:    service.ScopeGlobal,
		ShimsDir: "/usr/local/unirtm/shims",
		ToolVersions: map[string]string{
			"ruby": "3.2.0",
		},
	}

	script, err := manager.GenerateActivationScript(ctx, config)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Shell: %s\n", script.Shell)
	fmt.Println("Uses fish syntax:", containsString(script.Content, "set -gx"))
	fmt.Println("Contains ruby version:", containsString(script.Content, "UNIRTM_RUBY_VERSION"))

}

// Helper function for examples
func containsString(s, substr string) bool {
	return len(s) > 0 && len(substr) > 0 &&
		(s == substr || len(s) >= len(substr) &&
			(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
				findSubstring(s, substr)))
}

func findSubstring(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
Output:
Shell: fish
Uses fish syntax: true
Contains ruby version: true
Example (MultipleTools)

ExampleActivationManager_GenerateActivationScript_multipleTools demonstrates generating an activation script with multiple tools.

package main

import (
	"github.com/snowdreamtech/unirtm/internal/provider"

	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	manager := service.NewActivationManager("/usr/local/unirtm/shims", "/var/lib/unirtm", provider.NewRegistry())
	ctx := context.Background()

	config := service.ActivationConfig{
		Shell:    service.ShellBash,
		Scope:    service.ScopeGlobal,
		ShimsDir: "/usr/local/unirtm/shims",
		ToolVersions: map[string]string{
			"node":   "20.0.0",
			"python": "3.11.0",
			"go":     "1.21.0",
			"ruby":   "3.2.0",
		},
	}

	script, err := manager.GenerateActivationScript(ctx, config)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Shell: %s\n", script.Shell)
	fmt.Printf("Tool count: %d\n", len(config.ToolVersions))
	fmt.Println("Contains all tools:",
		containsString(script.Content, "UNIRTM_NODE_VERSION") &&
			containsString(script.Content, "UNIRTM_PYTHON_VERSION") &&
			containsString(script.Content, "UNIRTM_GO_VERSION") &&
			containsString(script.Content, "UNIRTM_RUBY_VERSION"))

}

// Helper function for examples
func containsString(s, substr string) bool {
	return len(s) > 0 && len(substr) > 0 &&
		(s == substr || len(s) >= len(substr) &&
			(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
				findSubstring(s, substr)))
}

func findSubstring(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
Output:
Shell: bash
Tool count: 4
Contains all tools: true
Example (Powershell)

ExampleActivationManager_GenerateActivationScript_powershell demonstrates generating an activation script for PowerShell.

package main

import (
	"github.com/snowdreamtech/unirtm/internal/provider"

	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	manager := service.NewActivationManager("/usr/local/unirtm/shims", "/var/lib/unirtm", provider.NewRegistry())
	ctx := context.Background()

	config := service.ActivationConfig{
		Shell:    service.ShellPowerShell,
		Scope:    service.ScopeGlobal,
		ShimsDir: "/usr/local/unirtm/shims",
		ToolVersions: map[string]string{
			"dotnet": "8.0.0",
		},
		EnvVars: map[string]string{
			"DOTNET_CLI_TELEMETRY_OPTOUT": "1",
		},
	}

	script, err := manager.GenerateActivationScript(ctx, config)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Shell: %s\n", script.Shell)
	fmt.Println("Uses PowerShell syntax:", containsString(script.Content, "$env:"))
	fmt.Println("Contains dotnet version:", containsString(script.Content, "UNIRTM_DOTNET_VERSION"))
	fmt.Println("Contains telemetry opt-out:", containsString(script.Content, "DOTNET_CLI_TELEMETRY_OPTOUT"))

}

// Helper function for examples
func containsString(s, substr string) bool {
	return len(s) > 0 && len(substr) > 0 &&
		(s == substr || len(s) >= len(substr) &&
			(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
				findSubstring(s, substr)))
}

func findSubstring(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
Output:
Shell: powershell
Uses PowerShell syntax: true
Contains dotnet version: true
Contains telemetry opt-out: true

func (*ActivationManager) GenerateGlobalActivation

func (m *ActivationManager) GenerateGlobalActivation(ctx context.Context, shell ShellType, toolVersions map[string]string) (*ActivationScript, error)

GenerateGlobalActivation generates activation for global (system-wide) tool versions.

Requirements: 15.5

Example

ExampleActivationManager_GenerateGlobalActivation demonstrates generating a global activation script for bash.

package main

import (
	"github.com/snowdreamtech/unirtm/internal/provider"

	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	manager := service.NewActivationManager("/usr/local/unirtm/shims", "/var/lib/unirtm", provider.NewRegistry())
	ctx := context.Background()

	toolVersions := map[string]string{
		"node":   "20.0.0",
		"python": "3.11.0",
		"go":     "1.21.0",
	}

	script, err := manager.GenerateGlobalActivation(ctx, service.ShellBash, toolVersions)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Shell: %s\n", script.Shell)
	fmt.Println("Script contains PATH modification:", containsString(script.Content, "export PATH="))
	fmt.Println("Script contains node version:", containsString(script.Content, "UNIRTM_NODE_VERSION"))
	fmt.Println("Script contains python version:", containsString(script.Content, "UNIRTM_PYTHON_VERSION"))
	fmt.Println("Script contains go version:", containsString(script.Content, "UNIRTM_GO_VERSION"))

}

// Helper function for examples
func containsString(s, substr string) bool {
	return len(s) > 0 && len(substr) > 0 &&
		(s == substr || len(s) >= len(substr) &&
			(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
				findSubstring(s, substr)))
}

func findSubstring(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
Output:
Shell: bash
Script contains PATH modification: true
Script contains node version: true
Script contains python version: true
Script contains go version: true

func (*ActivationManager) GenerateProjectActivation

func (m *ActivationManager) GenerateProjectActivation(ctx context.Context, shell ShellType, projectDir string, toolVersions map[string]string, envVars map[string]string) (*ActivationScript, error)

GenerateProjectActivation generates activation for project-specific tool versions.

Requirements: 15.4

Example

ExampleActivationManager_GenerateProjectActivation demonstrates generating a project-specific activation script.

package main

import (
	"github.com/snowdreamtech/unirtm/internal/provider"

	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	manager := service.NewActivationManager("/usr/local/unirtm/shims", "/var/lib/unirtm", provider.NewRegistry())
	ctx := context.Background()

	toolVersions := map[string]string{
		"node": "18.0.0",
	}

	envVars := map[string]string{
		"NODE_ENV": "development",
		"DEBUG":    "app:*",
	}

	script, err := manager.GenerateProjectActivation(
		ctx,
		service.ShellBash,
		"/home/user/myproject",
		toolVersions,
		envVars,
	)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Shell: %s\n", script.Shell)
	fmt.Println("Script contains project directory:", containsString(script.Content, "UNIRTM_PROJECT_DIR"))
	fmt.Println("Script contains NODE_ENV:", containsString(script.Content, "NODE_ENV"))
	fmt.Println("Script contains DEBUG:", containsString(script.Content, "DEBUG"))
	fmt.Println("Scope is project:", containsString(script.Content, "UNIRTM_ACTIVATION_SCOPE=\"project\""))

}

// Helper function for examples
func containsString(s, substr string) bool {
	return len(s) > 0 && len(substr) > 0 &&
		(s == substr || len(s) >= len(substr) &&
			(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
				findSubstring(s, substr)))
}

func findSubstring(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
Output:
Shell: bash
Script contains project directory: true
Script contains NODE_ENV: true
Script contains DEBUG: true
Scope is project: true

type ActivationScope

type ActivationScope string

ActivationScope represents the scope of activation.

const (
	// ScopeGlobal represents global (system-wide) activation
	ScopeGlobal ActivationScope = "global"
	// ScopeProject represents project-specific activation
	ScopeProject ActivationScope = "project"
)

type ActivationScript

type ActivationScript struct {
	// Shell is the target shell type
	Shell ShellType
	// Content is the script content
	Content string
	// Instructions are human-readable instructions for using the script
	Instructions string
}

ActivationScript represents a generated activation script.

type AuditLogEntry

type AuditLogEntry struct {
	// Operation is the type of operation being performed
	Operation OperationType

	// Tool is the name of the tool (optional, may be empty for non-tool operations)
	Tool string

	// Version is the version of the tool (optional)
	Version string

	// Status is the operation status (success or failure)
	Status OperationStatus

	// Error is the error message if the operation failed (optional)
	Error string

	// Duration is the operation duration in milliseconds
	Duration int64

	// GpgVerification is the result of GPG signature verification
	GpgVerification string

	// Metadata contains additional operation-specific data (optional)
	Metadata map[string]interface{}
}

AuditLogEntry represents a high-level audit log entry with convenience fields

type AuditService

type AuditService struct {
	// contains filtered or unexported fields
}

AuditService provides high-level audit logging functionality Validates Requirements: 7.8 (Audit logging), 8.1 (Log all operations), 8.5 (Audit log recording)

func NewAuditService

func NewAuditService(repo repository.AuditRepository) *AuditService

NewAuditService creates a new audit service

func (*AuditService) GetLogsByOperation

func (s *AuditService) GetLogsByOperation(ctx context.Context, operation OperationType, limit int) ([]*repository.AuditEntry, error)

GetLogsByOperation returns audit logs filtered by operation type

func (*AuditService) GetLogsByStatus

func (s *AuditService) GetLogsByStatus(ctx context.Context, status OperationStatus, limit int) ([]*repository.AuditEntry, error)

GetLogsByStatus returns audit logs filtered by status

Example

ExampleAuditService_GetLogsByStatus demonstrates how to query failed operations

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/snowdreamtech/unirtm/internal/database"
	"github.com/snowdreamtech/unirtm/internal/repository/sqlite"
	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	// Initialize database (in-memory for example)
	db, err := database.Open(context.Background(), database.Config{
		Path:    ":memory:",
		WALMode: false,
	})
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// Create audit repository
	auditRepo, err := sqlite.NewAuditRepository(db.Conn())
	if err != nil {
		panic(err)
	}
	defer auditRepo.Close()

	// Create audit service
	auditService := service.NewAuditService(auditRepo)

	ctx := context.Background()

	// Log successful and failed operations
	_ = auditService.LogInstall(ctx, "node", "20.0.0", 2*time.Second, nil)
	_ = auditService.LogInstall(ctx, "python", "3.11.0", 5*time.Second, fmt.Errorf("download failed"))
	_ = auditService.LogInstall(ctx, "go", "1.21.0", 3*time.Second, fmt.Errorf("checksum mismatch"))

	// Query failed operations
	failedLogs, err := auditService.GetLogsByStatus(ctx, service.StatusFailure, 10)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Found %d failed operations\n", len(failedLogs))
}
Output:
Found 2 failed operations

func (*AuditService) GetLogsByTimeRange

func (s *AuditService) GetLogsByTimeRange(ctx context.Context, startTime, endTime time.Time, limit int) ([]*repository.AuditEntry, error)

GetLogsByTimeRange returns audit logs within a time range

func (*AuditService) GetLogsByTool

func (s *AuditService) GetLogsByTool(ctx context.Context, tool string, limit int) ([]*repository.AuditEntry, error)

GetLogsByTool returns audit logs filtered by tool name

Example

ExampleAuditService_GetLogsByTool demonstrates how to query audit logs for a specific tool

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/snowdreamtech/unirtm/internal/database"
	"github.com/snowdreamtech/unirtm/internal/repository/sqlite"
	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	// Initialize database (in-memory for example)
	db, err := database.Open(context.Background(), database.Config{
		Path:    ":memory:",
		WALMode: false,
	})
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// Create audit repository
	auditRepo, err := sqlite.NewAuditRepository(db.Conn())
	if err != nil {
		panic(err)
	}
	defer auditRepo.Close()

	// Create audit service
	auditService := service.NewAuditService(auditRepo)

	ctx := context.Background()

	// Log operations for different tools
	_ = auditService.LogInstall(ctx, "node", "20.0.0", 2*time.Second, nil)
	_ = auditService.LogActivate(ctx, "node", "20.0.0", 100*time.Millisecond, nil)
	_ = auditService.LogInstall(ctx, "python", "3.11.0", 3*time.Second, nil)

	// Query logs for a specific tool
	nodeLogs, err := auditService.GetLogsByTool(ctx, "node", 10)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Found %d operations for node\n", len(nodeLogs))
}
Output:
Found 2 operations for node

func (*AuditService) GetRecentLogs

func (s *AuditService) GetRecentLogs(ctx context.Context, limit int) ([]*repository.AuditEntry, error)

GetRecentLogs returns the most recent audit logs

func (*AuditService) LogActivate

func (s *AuditService) LogActivate(ctx context.Context, tool, version string, duration time.Duration, err error) error

LogActivate logs a tool activation operation

func (*AuditService) LogCachePurge

func (s *AuditService) LogCachePurge(ctx context.Context, duration time.Duration, purgedCount int, err error) error

LogCachePurge logs a cache purge operation

func (*AuditService) LogConfigLoad

func (s *AuditService) LogConfigLoad(ctx context.Context, configPath string, duration time.Duration, err error) error

LogConfigLoad logs a configuration load operation

func (*AuditService) LogConfigUpdate

func (s *AuditService) LogConfigUpdate(ctx context.Context, configPath string, duration time.Duration, err error) error

LogConfigUpdate logs a configuration update operation

func (*AuditService) LogDeactivate

func (s *AuditService) LogDeactivate(ctx context.Context, tool, version string, duration time.Duration, err error) error

LogDeactivate logs a tool deactivation operation

func (*AuditService) LogIndexUpdate

func (s *AuditService) LogIndexUpdate(ctx context.Context, backend string, duration time.Duration, toolCount int, err error) error

LogIndexUpdate logs an index update operation

func (*AuditService) LogInstall

func (s *AuditService) LogInstall(ctx context.Context, tool, version string, duration time.Duration, err error) error

LogInstall logs a tool installation operation

Example

ExampleAuditService_LogInstall demonstrates how to use the audit service to log tool installations

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/snowdreamtech/unirtm/internal/database"
	"github.com/snowdreamtech/unirtm/internal/repository/sqlite"
	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	// Initialize database (in-memory for example)
	db, err := database.Open(context.Background(), database.Config{
		Path:    ":memory:",
		WALMode: false,
	})
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// Create audit repository
	auditRepo, err := sqlite.NewAuditRepository(db.Conn())
	if err != nil {
		panic(err)
	}
	defer auditRepo.Close()

	// Create audit service
	auditService := service.NewAuditService(auditRepo)

	// Log a successful installation
	ctx := context.Background()
	err = auditService.LogInstall(ctx, "node", "20.0.0", 2*time.Second, nil)
	if err != nil {
		panic(err)
	}

	// Log a failed installation
	installErr := fmt.Errorf("download failed: connection timeout")
	err = auditService.LogInstall(ctx, "python", "3.11.0", 5*time.Second, installErr)
	if err != nil {
		panic(err)
	}

	// Query recent logs
	logs, err := auditService.GetRecentLogs(ctx, 10)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Found %d audit log entries\n", len(logs))
}
Output:
Found 2 audit log entries

func (*AuditService) LogOperation

func (s *AuditService) LogOperation(ctx context.Context, entry *AuditLogEntry) error

LogOperation logs an operation to the audit log This is the primary method for recording audit entries

Example

ExampleAuditService_LogOperation demonstrates how to use the audit service with custom metadata

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/database"
	"github.com/snowdreamtech/unirtm/internal/repository/sqlite"
	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	// Initialize database (in-memory for example)
	db, err := database.Open(context.Background(), database.Config{
		Path:    ":memory:",
		WALMode: false,
	})
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// Create audit repository
	auditRepo, err := sqlite.NewAuditRepository(db.Conn())
	if err != nil {
		panic(err)
	}
	defer auditRepo.Close()

	// Create audit service
	auditService := service.NewAuditService(auditRepo)

	// Log an operation with custom metadata
	ctx := context.Background()
	entry := &service.AuditLogEntry{
		Operation: service.OperationInstall,
		Tool:      "node",
		Version:   "20.0.0",
		Status:    service.StatusSuccess,
		Duration:  2500,
		Metadata: map[string]interface{}{
			"backend":      "github",
			"download_url": "https://github.com/nodejs/node/releases/download/v20.0.0/node-v20.0.0.tar.gz",
			"size_bytes":   45678901,
			"checksum":     "abc123def456",
		},
	}

	err = auditService.LogOperation(ctx, entry)
	if err != nil {
		panic(err)
	}

	fmt.Println("Operation logged successfully")
}
Output:
Operation logged successfully

func (*AuditService) LogUninstall

func (s *AuditService) LogUninstall(ctx context.Context, tool, version string, duration time.Duration, err error) error

LogUninstall logs a tool uninstallation operation

func (*AuditService) LogUpdate

func (s *AuditService) LogUpdate(ctx context.Context, tool, oldVersion, newVersion string, duration time.Duration, err error) error

LogUpdate logs a tool update operation

func (*AuditService) LogVersionResolve

func (s *AuditService) LogVersionResolve(ctx context.Context, tool, versionSpec, resolvedVersion string, duration time.Duration, err error) error

LogVersionResolve logs a version resolution operation

func (*AuditService) QueryAuditLogs

func (s *AuditService) QueryAuditLogs(ctx context.Context, filter repository.AuditFilter) ([]*repository.AuditEntry, error)

QueryAuditLogs queries audit logs with filters

Example

ExampleAuditService_QueryAuditLogs demonstrates how to query audit logs with filters

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/snowdreamtech/unirtm/internal/database"
	"github.com/snowdreamtech/unirtm/internal/repository/sqlite"
	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	// Initialize database (in-memory for example)
	db, err := database.Open(context.Background(), database.Config{
		Path:    ":memory:",
		WALMode: false,
	})
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// Create audit repository
	auditRepo, err := sqlite.NewAuditRepository(db.Conn())
	if err != nil {
		panic(err)
	}
	defer auditRepo.Close()

	// Create audit service
	auditService := service.NewAuditService(auditRepo)

	ctx := context.Background()

	// Log some operations
	_ = auditService.LogInstall(ctx, "node", "20.0.0", 2*time.Second, nil)
	_ = auditService.LogInstall(ctx, "python", "3.11.0", 3*time.Second, nil)
	_ = auditService.LogUninstall(ctx, "go", "1.20.0", 1*time.Second, nil)

	// Query logs by operation type
	installLogs, err := auditService.GetLogsByOperation(ctx, service.OperationInstall, 10)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Found %d install operations\n", len(installLogs))
}
Output:
Found 2 install operations

type AutoActivationManager

type AutoActivationManager struct {
	// contains filtered or unexported fields
}

AutoActivationManager manages automatic environment activation based on directory changes.

It detects when the user enters a directory with a UniRTM configuration file and automatically activates the project's toolchain. When leaving the directory, it restores the previous environment.

Requirements: 15.6, 15.7

Example (Deactivation)

ExampleAutoActivationManager_deactivation demonstrates leaving a project.

package main

import (
	"github.com/snowdreamtech/unirtm/internal/provider"

	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	// Create temporary project directory
	tmpDir, _ := os.MkdirTemp("", "unirtm-example-*")
	defer os.RemoveAll(tmpDir)

	projectDir := filepath.Join(tmpDir, "myproject")
	_ = os.MkdirAll(projectDir, 0755)
	_ = os.WriteFile(filepath.Join(projectDir, "unirtm.toml"), []byte("# project"), 0644)

	// Create managers
	activationMgr := service.NewActivationManager("/tmp/shims", "/tmp/data", provider.NewRegistry())
	autoMgr := service.NewAutoActivationManager(activationMgr)

	// Start in project
	currentState := &service.EnvironmentState{
		ProjectDir: projectDir,
		ToolVersions: map[string]string{
			"python": "3.11.0",
		},
		EnvVars:      make(map[string]string),
		PreviousPath: "/usr/bin:/bin",
	}

	// Leave project
	event := service.DirectoryChangeEvent{
		OldDir: projectDir,
		NewDir: tmpDir,
		Shell:  service.ShellBash,
	}

	ctx := context.Background()
	change, err := autoMgr.HandleDirectoryChange(ctx, event, currentState)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Action: %s\n", change.Action)
	fmt.Printf("Project deactivated: %t\n", change.NewState.ProjectDir == "")
	fmt.Printf("Tools cleared: %t\n", len(change.NewState.ToolVersions) == 0)

}
Output:
Action: deactivate
Project deactivated: true
Tools cleared: true
Example (ProjectSwitch)

ExampleAutoActivationManager_projectSwitch demonstrates switching between projects.

package main

import (
	"github.com/snowdreamtech/unirtm/internal/provider"

	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	// Create temporary project directories
	tmpDir, _ := os.MkdirTemp("", "unirtm-example-*")
	defer os.RemoveAll(tmpDir)

	project1 := filepath.Join(tmpDir, "project1")
	project2 := filepath.Join(tmpDir, "project2")
	_ = os.MkdirAll(project1, 0755)
	_ = os.MkdirAll(project2, 0755)
	_ = os.WriteFile(filepath.Join(project1, "unirtm.toml"), []byte("# project1"), 0644)
	_ = os.WriteFile(filepath.Join(project2, "unirtm.toml"), []byte("# project2"), 0644)

	// Create managers
	activationMgr := service.NewActivationManager("/tmp/shims", "/tmp/data", provider.NewRegistry())
	autoMgr := service.NewAutoActivationManager(activationMgr)

	// Start in project1
	currentState := &service.EnvironmentState{
		ProjectDir: project1,
		ToolVersions: map[string]string{
			"node": "18.0.0",
		},
		EnvVars:      make(map[string]string),
		PreviousPath: "/usr/bin:/bin",
	}

	// Switch to project2
	event := service.DirectoryChangeEvent{
		OldDir: project1,
		NewDir: project2,
		Shell:  service.ShellBash,
	}

	ctx := context.Background()
	change, err := autoMgr.HandleDirectoryChange(ctx, event, currentState)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Action: %s\n", change.Action)
	fmt.Printf("Old project: %s\n", filepath.Base(currentState.ProjectDir))
	fmt.Printf("New project: %s\n", filepath.Base(change.NewState.ProjectDir))

}
Output:
Action: switch
Old project: project1
New project: project2

func NewAutoActivationManager

func NewAutoActivationManager(activationManager *ActivationManager) *AutoActivationManager

NewAutoActivationManager creates a new AutoActivationManager.

func (*AutoActivationManager) GenerateHookEnvScript

func (m *AutoActivationManager) GenerateHookEnvScript(shell ShellType, exePath string) (string, error)

GenerateHookEnvScript generates a shell hook script that can be evaluated on every prompt.

This script is designed to be evaluated by the shell on every prompt (e.g., via PROMPT_COMMAND in bash or precmd in zsh). It detects directory changes and outputs the appropriate activation or deactivation commands.

Requirements: 15.6, 15.7

Example

ExampleAutoActivationManager_GenerateHookEnvScript demonstrates how to generate a shell hook script for automatic activation.

package main

import (
	"github.com/snowdreamtech/unirtm/internal/provider"

	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	activationMgr := service.NewActivationManager("/tmp/shims", "/tmp/data", provider.NewRegistry())
	autoMgr := service.NewAutoActivationManager(activationMgr)

	// Generate hook script for bash
	hookScript, err := autoMgr.GenerateHookEnvScript(service.ShellBash, "/usr/local/bin/unirtm")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// The hook script can be added to ~/.bashrc
	fmt.Println("# Add this to your ~/.bashrc:")
	fmt.Println(hookScript)

	// Output will include:
	// # Add this to your ~/.bashrc:
	// # UniRTM auto-activation hook
	// _unirtm_hook() {
	//   ...
	// }
}

func (*AutoActivationManager) HandleDirectoryChange

func (m *AutoActivationManager) HandleDirectoryChange(ctx context.Context, event DirectoryChangeEvent, currentState *EnvironmentState) (*ActivationChange, error)

HandleDirectoryChange handles a directory change event and returns the activation changes needed.

This is the main entry point for auto-activation. It detects whether the directory change requires activating, deactivating, or switching project environments.

Requirements: 15.6, 15.7

Example

ExampleAutoActivationManager_HandleDirectoryChange demonstrates how to use the Auto-Activation Manager to handle directory changes and automatically activate project toolchains.

package main

import (
	"github.com/snowdreamtech/unirtm/internal/provider"

	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	// Create temporary project directory for demonstration
	tmpDir, _ := os.MkdirTemp("", "unirtm-example-*")
	defer os.RemoveAll(tmpDir)

	projectDir := filepath.Join(tmpDir, "myproject")
	_ = os.MkdirAll(projectDir, 0755)
	_ = os.WriteFile(filepath.Join(projectDir, "unirtm.toml"), []byte("# project config"), 0644)

	// Create managers
	activationMgr := service.NewActivationManager("/tmp/shims", "/tmp/data", provider.NewRegistry())
	autoMgr := service.NewAutoActivationManager(activationMgr)

	// Initial state (no project active)
	currentState := &service.EnvironmentState{
		ProjectDir:   "",
		ToolVersions: make(map[string]string),
		EnvVars:      make(map[string]string),
		PreviousPath: "/usr/bin:/bin",
	}

	// Simulate entering the project directory
	event := service.DirectoryChangeEvent{
		OldDir: tmpDir,
		NewDir: projectDir,
		Shell:  service.ShellBash,
	}

	ctx := context.Background()
	change, err := autoMgr.HandleDirectoryChange(ctx, event, currentState)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Action: %s\n", change.Action)
	fmt.Printf("Project sub-directory: %s\n", filepath.Base(change.NewState.ProjectDir))
	fmt.Printf("Script generated: %t\n", len(change.Script) > 0)

}
Output:
Action: activate
Project sub-directory: myproject
Script generated: true

func (*AutoActivationManager) LoadConfigByDir

func (m *AutoActivationManager) LoadConfigByDir(projectDir string) (map[string]string, map[string]string, []string, error)

LoadConfigByDir loads UniRTM configuration from the specified directory.

type BackendRegistry

type BackendRegistry interface {
	// ResolveVersion resolves a version specification to a concrete version
	ResolveVersion(ctx context.Context, tool string, versionSpec string) (string, error)
}

BackendRegistry defines the interface for accessing backend information.

type CacheManager

type CacheManager struct {
	// contains filtered or unexported fields
}

CacheManager manages cache storage with TTL, checksum verification, and automatic cleanup Validates Requirements: 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8

func NewCacheManager

func NewCacheManager(repo repository.CacheRepository, auditRepo repository.AuditRepository, config CacheManagerConfig) (*CacheManager, error)

NewCacheManager creates a new cache manager instance

func (*CacheManager) AutoCleanup

func (cm *CacheManager) AutoCleanup(ctx context.Context) error

AutoCleanup performs automatic cleanup when cache size exceeds threshold Validates Requirement: 10.7 (Support automatic cleanup when size exceeds threshold)

func (*CacheManager) Delete

func (cm *CacheManager) Delete(ctx context.Context, key string) error

Delete removes a cache entry

func (*CacheManager) Get

func (cm *CacheManager) Get(ctx context.Context, key string) ([]byte, error)

Get retrieves a cache entry with checksum verification Validates Requirements: 10.4 (Verify checksum before use), 10.8 (Track cache hits/misses)

func (*CacheManager) GetCacheSize

func (cm *CacheManager) GetCacheSize() (int64, error)

GetCacheSize returns the current cache size in bytes Validates Requirement: 10.7 (Track cache size)

func (*CacheManager) GetStats

func (cm *CacheManager) GetStats() CacheStats

GetStats returns the current cache statistics Validates Requirement: 10.8 (Record cache hits and misses for performance monitoring)

func (*CacheManager) GetWithChecksum

func (cm *CacheManager) GetWithChecksum(ctx context.Context, key string, expectedChecksum string) ([]byte, error)

GetWithChecksum retrieves a cache entry and verifies its checksum Validates Requirements: 10.4 (Verify checksum), 10.5 (Delete and re-download on failure)

func (*CacheManager) PurgeAll

func (cm *CacheManager) PurgeAll(ctx context.Context) error

PurgeAll removes all cache entries Validates Requirement: 10.6 (Support manual cache purging - clear all)

func (*CacheManager) PurgeByPrefix

func (cm *CacheManager) PurgeByPrefix(ctx context.Context, prefix string) error

PurgeByPrefix removes all cache entries with keys matching the given prefix Validates Requirement: 10.6 (Support manual cache purging - clear tool-specific)

func (*CacheManager) PurgeExpired

func (cm *CacheManager) PurgeExpired(ctx context.Context) error

PurgeExpired removes all expired cache entries Validates Requirement: 10.6 (Support manual cache purging - clear expired)

func (*CacheManager) ResetStats

func (cm *CacheManager) ResetStats()

ResetStats resets the cache statistics

func (*CacheManager) Set

func (cm *CacheManager) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error

Set stores a cache entry with TTL and saves the artifact to disk Validates Requirements: 10.1 (Store downloaded tarballs), 10.2 (Store metadata with TTL)

type CacheManagerConfig

type CacheManagerConfig struct {
	// CacheDir is the directory where cached artifacts are stored
	CacheDir string
	// MaxCacheSize is the maximum cache size in bytes (default 5GB)
	MaxCacheSize int64
}

CacheManagerConfig holds configuration for the cache manager

type CacheStats

type CacheStats struct {
	Hits   int64
	Misses int64
	// contains filtered or unexported fields
}

CacheStats tracks cache hit/miss statistics for performance monitoring Validates Requirement: 10.8 (Record cache hits and misses)

type ChecksumAlgorithm

type ChecksumAlgorithm string

ChecksumAlgorithm represents a checksum algorithm.

const (
	AlgorithmSHA256 ChecksumAlgorithm = "sha256"
	AlgorithmSHA512 ChecksumAlgorithm = "sha512"
)

type CleanupManager

type CleanupManager struct {
	// contains filtered or unexported fields
}

CleanupManager removes orphaned files and partial installations.

Validates Requirement: 3.4

func NewCleanupManager

func NewCleanupManager(installRepo repository.InstallationRepository, installsDir string) *CleanupManager

NewCleanupManager creates a new CleanupManager.

func (*CleanupManager) CleanOrphaned

func (cm *CleanupManager) CleanOrphaned(ctx context.Context, dryRun bool) ([]string, error)

CleanOrphaned removes orphaned installation directories.

Validates Requirement: 3.4

func (*CleanupManager) FindOrphaned

func (cm *CleanupManager) FindOrphaned(ctx context.Context) ([]string, error)

FindOrphaned finds installation directories that have no corresponding database record.

Validates Requirement: 3.4

type ConcurrentInstallResult

type ConcurrentInstallResult struct {
	Tool    string
	Version string
	Success bool
	Error   string
}

ConcurrentInstallResult represents the result of a single concurrent installation.

type ConcurrentManager

type ConcurrentManager struct {
	// contains filtered or unexported fields
}

ConcurrentManager manages parallel tool installations with controlled concurrency.

It respects dependency order, limits concurrent operations to avoid resource exhaustion, serializes database writes, and reports progress per-operation.

Validates Requirements: 18.1, 18.2, 18.3, 18.4, 18.5, 18.6, 18.7

func NewConcurrentManager

func NewConcurrentManager(im Installer, config ConcurrentManagerConfig) *ConcurrentManager

NewConcurrentManager creates a new ConcurrentManager.

Validates Requirement: 18.2 (configurable concurrency limit)

func (*ConcurrentManager) InstallAll

InstallAll installs multiple tools concurrently, respecting dependency order.

It:

  1. Topologically sorts by DependsOn to determine installation order (Req 18.6)
  2. Uses errgroup for parallel execution within each dependency layer (Req 18.1)
  3. Limits concurrency to maxConcurrency (Req 18.2)
  4. Reports progress via progressFn (Req 18.5)
  5. Cancels dependents if a dependency fails (Req 18.7)

Validates Requirements: 18.1, 18.2, 18.4, 18.5, 18.6, 18.7

type ConcurrentManagerConfig

type ConcurrentManagerConfig struct {
	// MaxConcurrency is the maximum parallel operations (0 = CPU count).
	MaxConcurrency int
	// ProgressFn is an optional progress callback.
	ProgressFn func(tool, version, status string)
}

ConcurrentManagerConfig holds configuration for ConcurrentManager.

type ConfigValidationResult

type ConfigValidationResult struct {
	// Issues contains all detected problems (errors and warnings).
	Issues []ValidationIssue
	// Valid is true only when there are no error-severity issues.
	Valid bool
}

ConfigValidationResult holds all issues found during validation.

Validates Requirements: 13.1–13.7

func (*ConfigValidationResult) HasErrors

func (r *ConfigValidationResult) HasErrors() bool

HasErrors returns true if any error-severity issues exist.

func (*ConfigValidationResult) Summary

func (r *ConfigValidationResult) Summary() string

Summary returns a concise human-readable summary of the validation result.

type ConfigValidator

type ConfigValidator struct {
	// contains filtered or unexported fields
}

ConfigValidator performs semantic validation of UniRTM configuration.

It goes beyond basic TOML syntax to verify:

  • Tool names exist in the index (Req 13.1)
  • Version specifiers are syntactically valid (Req 13.2)
  • Backend references are registered (Req 13.3)
  • Unknown top-level fields generate warnings (Req 13.4)
  • Environment references are valid (Req 13.5)
  • Validation-only mode (Req 13.6)
  • All errors reported, not just first (Req 13.7)

Validates Requirements: 13.1, 13.2, 13.3, 13.4, 13.5, 13.6, 13.7

func NewConfigValidator

func NewConfigValidator(indexRepo repository.IndexRepository, registeredBackends []string) *ConfigValidator

NewConfigValidator creates a new ConfigValidator.

func (*ConfigValidator) Validate

Validate performs full semantic validation of the given configuration.

All issues are collected and returned (Req 13.7 — report all, not just first).

type ConflictingRequester

type ConflictingRequester struct {
	Tool             string
	RequestedVersion string
}

ConflictingRequester represents a tool that requires a specific version.

type Dependency

type Dependency struct {
	Tool              string // Tool name
	VersionConstraint string // Version constraint (e.g., ">=1.20.0", "^3.11", "latest")
}

Dependency represents a tool dependency with version constraints.

type DependencyConflict

type DependencyConflict struct {
	Tool       string
	Requesters []ConflictingRequester
}

DependencyConflict represents a version conflict between dependencies.

type DependencyGraph

type DependencyGraph struct {
	// contains filtered or unexported fields
}

DependencyGraph represents the dependency relationships between tools.

type DependencyResolver

type DependencyResolver struct {
	// contains filtered or unexported fields
}

DependencyResolver manages tool dependency resolution and installation ordering. It parses dependency declarations, builds dependency graphs, detects circular dependencies, and determines the correct installation order using topological sort.

func NewDependencyResolver

func NewDependencyResolver(
	providerRegistry ProviderRegistry,
	backendRegistry BackendRegistry,
	versionManager *VersionManager,
) *DependencyResolver

NewDependencyResolver creates a new DependencyResolver.

func (*DependencyResolver) DetectCircularDependencies

func (r *DependencyResolver) DetectCircularDependencies(graph *DependencyGraph) error

DetectCircularDependencies checks for circular dependencies in the graph. Returns an error if a circular dependency is detected.

Validates: Requirement 16.3

func (*DependencyResolver) ParseDependencies

func (r *DependencyResolver) ParseDependencies(ctx context.Context, tools []string) (*DependencyGraph, error)

ParseDependencies parses dependency declarations from provider metadata for the given tools. It returns a dependency graph containing all tools and their dependencies.

Validates: Requirement 16.1

func (*DependencyResolver) ResolveDependencies

func (r *DependencyResolver) ResolveDependencies(ctx context.Context, tools []string, requestedVersions map[string]string) (*InstallationOrder, error)

ResolveDependencies is the main entry point that combines all dependency resolution steps. It parses dependencies, detects circular dependencies, resolves version constraints, and returns the installation order.

Validates: Requirements 16.1, 16.2, 16.3, 16.4, 16.5, 16.6, 16.7

func (*DependencyResolver) ResolveVersionConstraints

func (r *DependencyResolver) ResolveVersionConstraints(ctx context.Context, graph *DependencyGraph, requestedVersions map[string]string) error

ResolveVersionConstraints resolves version constraints for all tools in the dependency graph. It checks for conflicts where multiple tools depend on different versions of the same tool.

Validates: Requirements 16.6, 16.7

func (*DependencyResolver) TopologicalSort

func (r *DependencyResolver) TopologicalSort(graph *DependencyGraph) (*InstallationOrder, error)

TopologicalSort performs a topological sort on the dependency graph to determine the correct installation order. Tools with no dependencies come first, followed by tools that depend on them.

Validates: Requirement 16.4

type DirectoryChangeEvent

type DirectoryChangeEvent struct {
	// OldDir is the previous working directory
	OldDir string
	// NewDir is the new working directory
	NewDir string
	// Shell is the current shell type
	Shell ShellType
}

DirectoryChangeEvent represents a directory change event.

type EnvironmentState

type EnvironmentState struct {
	// ProjectDir is the project directory (empty if no project is active)
	ProjectDir string
	// ToolVersions maps tool names to their active versions
	ToolVersions map[string]string
	// EnvVars contains environment variables set by the activation
	EnvVars map[string]string
	// PreviousPath is the PATH before activation
	PreviousPath string
}

EnvironmentState represents the state of the environment at a point in time.

type GenerateOptions

type GenerateOptions struct {
	// Tools is the subset of tools to refresh.  Empty = all tools from config.
	Tools []string
	// Platforms is the list of platform keys to generate entries for.
	// Empty = only current platform.  Use lockfile.StandardPlatforms for all.
	Platforms []string
}

GenerateOptions controls what `unirtm lock` generates.

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

Generator creates shim scripts for installed tools.

func NewGenerator

func NewGenerator(shimsDir, installsDir string) *Generator

NewGenerator creates a new shim Generator.

func (*Generator) GenerateShim

func (g *Generator) GenerateShim(ctx context.Context, tool string, executables ...string) error

GenerateShim creates a shim script for the given tool.

On Unix systems it creates a bash/sh script; on Windows it creates both a .cmd batch file and a .ps1 PowerShell script.

Validates Requirements: 14.1, 14.2, 14.3, 14.4

func (*Generator) ListShims

func (g *Generator) ListShims() ([]string, error)

ListShims returns all tool names that have shim scripts.

func (*Generator) RemoveShim

func (g *Generator) RemoveShim(ctx context.Context, tool string) error

RemoveShim removes the shim script(s) for the given tool.

func (*Generator) ShimExists

func (g *Generator) ShimExists(tool string) bool

ShimExists reports whether a shim script exists for the given tool.

type IncompleteOperation

type IncompleteOperation struct {
	// Tool is the name of the tool that was being installed/updated.
	Tool string
	// Version is the version that was being installed/updated.
	Version string
	// PartialPath is the path of the partial installation (if any).
	PartialPath string
	// StartedAt is when the operation started.
	StartedAt time.Time
	// AuditID is the ID of the audit log entry for this operation.
	AuditID int64
}

IncompleteOperation represents an operation that did not complete successfully.

type IndexManager

type IndexManager struct {
	// contains filtered or unexported fields
}

IndexManager manages tool index storage, retrieval, and updates Validates Requirements: 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8

Example (BackendManagement)

This example demonstrates backend management

package main

import (
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	// Create index manager
	indexManager, err := service.NewIndexManager(
		nil, // indexRepo
		nil, // auditRepo
		nil, // backends
		service.IndexManagerConfig{},
	)
	if err != nil {
		fmt.Println(err)
	}

	// Register a backend
	// githubBackend := backend.NewGitHubBackend(...)
	// indexManager.RegisterBackend("github", githubBackend)

	// List registered backends
	backends := indexManager.ListBackends()
	fmt.Printf("Registered backends: %v\n", backends)

	// Unregister a backend
	indexManager.UnregisterBackend("github")
}
Example (Basic)

This example demonstrates basic tool index management operations

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	// Note: This example uses mock implementations for demonstration
	// In production, use actual repository and backend implementations

	ctx := context.Background()

	// Create index manager (using mocks for example)
	indexManager, err := service.NewIndexManager(
		nil, // indexRepo - use actual implementation
		nil, // auditRepo - use actual implementation
		nil, // backends - register actual backends
		service.IndexManagerConfig{},
	)
	if err != nil {
		fmt.Println(err)
	}

	// Add a tool to the index
	err = indexManager.UpsertTool(ctx,
		"node",
		"Node.js JavaScript runtime",
		"https://nodejs.org",
		"MIT",
		"github",
		&service.ToolMetadata{
			AvailableVersions: []string{"20.0.0", "18.0.0", "16.0.0"},
			Tags:              []string{"runtime", "javascript", "nodejs"},
			Stars:             95000,
		},
	)
	if err != nil {
		fmt.Println(err)
	}

	// Retrieve the tool
	entry, err := indexManager.GetTool(ctx, "node")
	if err != nil {
		fmt.Println(err)
	}

	fmt.Printf("Tool: %s\n", entry.Tool)
	fmt.Printf("Description: %s\n", entry.Description)
	fmt.Printf("Backend: %s\n", entry.Backend)
}
Example (FilterByBackend)

This example demonstrates filtering tools by backend

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	ctx := context.Background()

	// Create index manager
	indexManager, err := service.NewIndexManager(
		nil, // indexRepo
		nil, // auditRepo
		nil, // backends
		service.IndexManagerConfig{},
	)
	if err != nil {
		fmt.Println(err)
	}

	// Filter tools by backend
	githubTools, err := indexManager.FilterByBackend(ctx, "github")
	if err != nil {
		fmt.Println(err)
	}

	fmt.Printf("Found %d tools from GitHub backend\n", len(githubTools))
}
Example (Metadata)

This example demonstrates getting tool metadata

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	ctx := context.Background()

	// Create index manager
	indexManager, err := service.NewIndexManager(
		nil, // indexRepo
		nil, // auditRepo
		nil, // backends
		service.IndexManagerConfig{},
	)
	if err != nil {
		fmt.Println(err)
	}

	// Get tool metadata
	metadata, err := indexManager.GetToolMetadata(ctx, "node")
	if err != nil {
		fmt.Println(err)
	}

	fmt.Printf("Available versions: %v\n", metadata.AvailableVersions)
	fmt.Printf("Tags: %v\n", metadata.Tags)
	fmt.Printf("Stars: %d\n", metadata.Stars)
}
Example (Offline)

This example demonstrates offline operation

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	ctx := context.Background()

	// Create index manager
	indexManager, err := service.NewIndexManager(
		nil, // indexRepo
		nil, // auditRepo
		nil, // backends
		service.IndexManagerConfig{},
	)
	if err != nil {
		fmt.Println(err)
	}

	// Check if offline operation is possible
	capable, err := indexManager.IsOfflineCapable(ctx)
	if err != nil {
		fmt.Println(err)
	}

	if !capable {
		fmt.Println("No cached index available. Please run 'unirtm index update' when online.")
		return
	}

	// Search offline
	results, err := indexManager.SearchTools(ctx, service.SearchOptions{
		Query: "python",
	})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Printf("Found %d tools matching 'python'\n", len(results))
}
Example (Pagination)

This example demonstrates pagination

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	ctx := context.Background()

	// Create index manager
	indexManager, err := service.NewIndexManager(
		nil, // indexRepo
		nil, // auditRepo
		nil, // backends
		service.IndexManagerConfig{},
	)
	if err != nil {
		fmt.Println(err)
	}

	// Search with pagination
	pageSize := 10
	page := 0

	for {
		results, err := indexManager.SearchTools(ctx, service.SearchOptions{
			Query:  "runtime",
			Limit:  pageSize,
			Offset: page * pageSize,
		})
		if err != nil {
			fmt.Println(err)
		}

		if len(results) == 0 {
			break
		}

		fmt.Printf("Page %d: %d results\n", page+1, len(results))
		for _, tool := range results {
			fmt.Printf("  - %s\n", tool.Tool)
		}

		page++
	}
}
Example (StaleDetection)

This example demonstrates stale index detection

package main

import (
	"context"
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	ctx := context.Background()

	// Create index manager with 7-day stale timeout
	indexManager, err := service.NewIndexManager(
		nil, // indexRepo
		nil, // auditRepo
		nil, // backends
		service.IndexManagerConfig{
			StaleTimeout: 7 * 24 * 60 * 60 * 1000000000, // 7 days in nanoseconds
		},
	)
	if err != nil {
		fmt.Println(err)
	}

	// Check if index is stale
	isStale, err := indexManager.IsStale(ctx)
	if err != nil {
		fmt.Println(err)
	}

	if isStale {
		fmt.Println("Index is stale, updating...")
		err = indexManager.UpdateFromAllBackends(ctx)
		if err != nil {
			fmt.Printf("Failed to update index: %v", err)
		}
	}
}

func NewIndexManager

func NewIndexManager(repo repository.IndexRepository, auditRepo repository.AuditRepository, backends map[string]backend.Backend, config IndexManagerConfig) (*IndexManager, error)

NewIndexManager creates a new index manager instance

func (*IndexManager) DeleteTool

func (im *IndexManager) DeleteTool(ctx context.Context, tool string) error

DeleteTool removes a tool from the index

func (*IndexManager) FilterByBackend

func (im *IndexManager) FilterByBackend(ctx context.Context, backendName string) ([]*repository.IndexEntry, error)

FilterByBackend filters tools by backend type Validates Requirement: 11.5 (Filter by backend type)

func (*IndexManager) GetStaleAge

func (im *IndexManager) GetStaleAge(ctx context.Context) (time.Duration, error)

GetStaleAge returns how long ago the index was last updated

func (*IndexManager) GetTool

func (im *IndexManager) GetTool(ctx context.Context, tool string) (*repository.IndexEntry, error)

GetTool retrieves a tool from the index by name

func (*IndexManager) GetToolMetadata

func (im *IndexManager) GetToolMetadata(ctx context.Context, tool string) (*ToolMetadata, error)

GetToolMetadata retrieves and parses the metadata for a tool

func (*IndexManager) IsOfflineCapable

func (im *IndexManager) IsOfflineCapable(ctx context.Context) (bool, error)

IsOfflineCapable checks if the index has cached data for offline operation Validates Requirement: 11.8 (Support offline operation using cached index)

func (*IndexManager) IsStale

func (im *IndexManager) IsStale(ctx context.Context) (bool, error)

IsStale checks if the index is stale (older than the configured timeout) Validates Requirement: 11.7 (Detect stale index)

func (*IndexManager) ListBackends

func (im *IndexManager) ListBackends() []string

ListBackends returns the names of all registered backends

func (*IndexManager) ListTools

func (im *IndexManager) ListTools(ctx context.Context) ([]*repository.IndexEntry, error)

ListTools lists all tools in the index Validates Requirement: 11.1 (Maintain searchable index)

func (*IndexManager) PromptForUpdate

func (im *IndexManager) PromptForUpdate(ctx context.Context) (bool, string, error)

PromptForUpdate checks if the index is stale and returns a prompt message Validates Requirement: 11.7 (Prompt for update when stale)

func (*IndexManager) RegisterBackend

func (im *IndexManager) RegisterBackend(backendName string, b backend.Backend)

RegisterBackend registers a backend for index updates

func (*IndexManager) SearchTools

func (im *IndexManager) SearchTools(ctx context.Context, options SearchOptions) ([]*repository.IndexEntry, error)

SearchTools searches for tools by name, description, or tags Validates Requirement: 11.4 (Search by name, description, tags)

func (*IndexManager) SupportsOffline

func (im *IndexManager) SupportsOffline() bool

SupportsOffline indicates whether the index manager can operate offline Validates Requirement: 11.8 (Support offline operation)

func (*IndexManager) UnregisterBackend

func (im *IndexManager) UnregisterBackend(backendName string)

UnregisterBackend removes a backend from the index manager

func (*IndexManager) UpdateFromAllBackends

func (im *IndexManager) UpdateFromAllBackends(ctx context.Context) error

UpdateFromAllBackends updates the index from all registered backends Validates Requirement: 11.2 (Update from multiple sources)

func (*IndexManager) UpdateFromBackend

func (im *IndexManager) UpdateFromBackend(ctx context.Context, backendName string) error

UpdateFromBackend updates the index from a specific backend Validates Requirement: 11.2 (Update from multiple sources)

func (*IndexManager) UpsertTool

func (im *IndexManager) UpsertTool(ctx context.Context, tool string, description string, homepage string, license string, backendName string, metadata *ToolMetadata) error

UpsertTool creates or updates a tool in the index Validates Requirement: 11.1 (Maintain searchable index)

type IndexManagerConfig

type IndexManagerConfig struct {
	// StaleTimeout is the duration after which the index is considered stale (default 7 days)
	StaleTimeout time.Duration
}

IndexManagerConfig holds configuration for the index manager

type InstallationManager

type InstallationManager struct {
	// contains filtered or unexported fields
}

InstallationManager manages tool installation workflow.

func NewInstallationManager

func NewInstallationManager(
	backendRegistry *backend.Registry,
	providerRegistry *provider.Registry,
	downloadManager *download.Manager,
	installRepo repository.InstallationRepository,
	txManager transaction.TransactionManager,
	settings *config.Settings,
) *InstallationManager

NewInstallationManager creates a new installation manager without lockfile support.

func NewInstallationManagerWithLock

func NewInstallationManagerWithLock(
	backendRegistry *backend.Registry,
	providerRegistry *provider.Registry,
	downloadManager *download.Manager,
	installRepo repository.InstallationRepository,
	txManager transaction.TransactionManager,
	lockService *LockService,
	settings *config.Settings,
) *InstallationManager

NewInstallationManagerWithLock creates an InstallationManager that reads and writes unirtm.lock for reproducible, API-call-free installations.

func (*InstallationManager) AutoDetectBackend

func (im *InstallationManager) AutoDetectBackend(toolName string) string

AutoDetectBackend attempts to identify the best backend for a given tool name.

func (*InstallationManager) Close added in v0.3.6

func (im *InstallationManager) Close() error

Close releases the underlying database connection held by the manager. It is safe to call Close on a nil or already-closed manager.

func (*InstallationManager) EnsureInstalled

func (im *InstallationManager) EnsureInstalled(ctx context.Context, tools map[string]config.ToolConfig) error

EnsureInstalled checks if all tools in the configuration are installed, and installs any missing ones.

func (*InstallationManager) EnsureInstalledFromSpecs

func (im *InstallationManager) EnsureInstalledFromSpecs(ctx context.Context, tools map[string]ToolSpec) error

EnsureInstalledFromSpecs checks if all tools in the specs are installed, and installs any missing ones.

func (*InstallationManager) Install

func (im *InstallationManager) Install(ctx context.Context, toolKey, tool, version, backendName string) error

Install performs the complete installation workflow for a tool. Workflow: check → download → verify → extract → activate → record

func (*InstallationManager) IsInstalled

func (im *InstallationManager) IsInstalled(ctx context.Context, tool, versionSpec, backendName string) (bool, *repository.Installation)

IsInstalled checks if a tool version is installed, considering version variants (v-prefix).

func (*InstallationManager) ParseToolSpec

func (im *InstallationManager) ParseToolSpec(spec string) (backend, tool, version string, explicit bool)

ParseToolSpec parses a tool specification string (e.g., "node@20", "github:cli/cli@v2.0.0") into its constituent parts: backend, tool name, version, and whether the version was explicit.

func (*InstallationManager) ResolveExecutable

func (im *InstallationManager) ResolveExecutable(ctx context.Context, exeName string, platform backend.Platform) (string, map[string]string, error)

ResolveExecutable finds the absolute path and environment variables for a given executable name by searching through installed tools in the current context.

func (*InstallationManager) ResolveToolEnvBySpec

func (im *InstallationManager) ResolveToolEnvBySpec(
	toolName, version, backendName string,
) map[string]string

ResolveToolEnvBySpec returns the environment variables exported by an installed tool identified by its name, version, and backend. It is used by the exec sub-command to inject per-tool environment variables (e.g. GOROOT, JAVA_HOME, UNIRTM_<TOOL>_VERSION) and bin-directory PATH entries.

If the tool is not installed or the provider does not export env vars the function returns an empty (non-nil) map and a nil error.

func (*InstallationManager) SelectVersionInteractive

func (im *InstallationManager) SelectVersionInteractive(ctx context.Context, tool, backendName string) (string, error)

SelectVersionInteractive opens an interactive menu to select a tool version.

func (*InstallationManager) SetAliases

func (im *InstallationManager) SetAliases(aliases map[string]map[string]string)

SetAliases sets the version aliases for tools.

func (*InstallationManager) SetDB added in v0.3.6

func (im *InstallationManager) SetDB(db *database.DB)

SetDB stores the underlying database handle so it can be closed with Close(). This must be called after NewInstallationManagerWithLock when the caller owns the database lifetime.

func (*InstallationManager) SetToolConfigs

func (im *InstallationManager) SetToolConfigs(toolConfigs map[string]config.ToolConfig)

SetToolConfigs sets the tool configurations for hooks.

func (*InstallationManager) SortTools

func (im *InstallationManager) SortTools(tools map[string]config.ToolConfig) []ToolToInstall

SortTools sorts tools such that dependencies are installed before dependent tools.

func (*InstallationManager) SortToolsFromSpecs

func (im *InstallationManager) SortToolsFromSpecs(tools map[string]ToolSpec) []ToolToInstall

SortToolsFromSpecs sorts tools such that dependencies are installed before dependent tools.

func (*InstallationManager) Uninstall

func (im *InstallationManager) Uninstall(ctx context.Context, tool, version string) error

Uninstall removes a tool installation.

type InstallationOrder

type InstallationOrder struct {
	// Tools is the ordered list of tool names
	Tools []string
	// Versions maps tool names to their resolved versions
	Versions map[string]string
}

InstallationOrder represents the ordered list of tools to install.

type Installer added in v0.2.0

type Installer interface {
	IsInstalled(ctx context.Context, tool, version, backend string) (bool, *repository.Installation)
	Install(ctx context.Context, toolKey, tool, version, backend string) error
}

Installer defines the installation interface required by ConcurrentManager.

type LockService

type LockService struct {
	// contains filtered or unexported fields
}

LockService manages the unirtm.lock file lifecycle:

  • Resolving download info from the lockfile (bypassing remote API calls)
  • Writing back resolved info after a successful install
  • Generating / refreshing lockfile entries for multiple platforms
  • Enforcing strict mode (UNIRTM_LOCKED=1): fail if URL not in lockfile

func NewLockService

func NewLockService(opts LockServiceOptions) (*LockService, error)

NewLockService creates a LockService, loading the lockfile from disk if it exists.

func (*LockService) CheckStrict

func (ls *LockService) CheckStrict(lockKey, version string, platform backend.Platform) error

CheckStrict verifies that the lockfile contains a URL for the given lockKey on the current platform when strict mode is enabled. Returns an error (which callers must propagate) when the URL is absent.

func (*LockService) Generate

func (ls *LockService) Generate(
	ctx context.Context,
	tools map[string]ToolSpec,
	opts GenerateOptions,
) error

Generate resolves download info from the backend for each (tool, platform) pair and writes the result into the lockfile.

ctx is used for backend API calls (cancellation, deadline). tools is a map of toolName → {version, backendName} from the project config.

func (*LockService) IsEmpty

func (ls *LockService) IsEmpty() bool

IsEmpty reports whether the lockfile contains any tool entries.

func (*LockService) IsStrictMode

func (ls *LockService) IsStrictMode() bool

IsStrictMode reports whether strict mode is active.

func (*LockService) Path

func (ls *LockService) Path() string

Path returns the filesystem path of the managed lockfile.

func (*LockService) RecordInstall

func (ls *LockService) RecordInstall(
	lockKey, backendName string,
	info *backend.VersionInfo,
) error

RecordInstall writes the resolved VersionInfo back into the lockfile for the current platform. The lockfile is saved to disk immediately so that subsequent runs can use the cached URL.

This is called by InstallationManager after a successful download.

func (*LockService) RemoveTool

func (ls *LockService) RemoveTool(lockKey string) error

RemoveTool removes all lockfile entries for a tool (called on uninstall).

func (*LockService) Resolve

func (ls *LockService) Resolve(
	lockKey, version string,
	platform backend.Platform,
) (*backend.VersionInfo, bool)

Resolve returns a *backend.VersionInfo populated from the lockfile for the given lockKey, version and current platform. Returns (nil, false) when the lockfile has no entry for this combination.

Callers (InstallationManager) should use the returned VersionInfo directly and skip the remote backend API call when ok==true.

func (*LockService) SetBackendRegistry

func (ls *LockService) SetBackendRegistry(r *backend.Registry)

SetBackendRegistry wires the backend registry used by Generate().

func (*LockService) Validate

func (ls *LockService) Validate() error

Validate runs structural validation on the in-memory lockfile.

type LockServiceOptions

type LockServiceOptions struct {
	// LockfilePath overrides the default lock file location.
	// When empty, GetLockFilePath() from env/paths is used.
	LockfilePath string

	// StrictMode mirrors the UNIRTM_LOCKED env var / settings.locked setting.
	// When true, Install() will fail if the tool+platform URL is absent from
	// the lockfile (preventing any outbound API call).
	StrictMode bool
}

LockServiceOptions configures a LockService.

type MigrationManager

type MigrationManager struct{}

MigrationManager converts mise/asdf configuration to UniRTM format.

Validates Requirements: 21.1, 21.2, 21.3, 21.4, 21.5, 21.6, 21.7

func NewMigrationManager

func NewMigrationManager() *MigrationManager

NewMigrationManager creates a new MigrationManager.

func (*MigrationManager) FormatReport

func (mm *MigrationManager) FormatReport(report *MigrationReport) string

FormatReport returns a human-readable migration report string.

Validates Requirement: 21.7 (generate migration report)

func (*MigrationManager) MigrateDirectory

func (mm *MigrationManager) MigrateDirectory(ctx context.Context, dir string, dryRun bool) ([]*MigrationReport, error)

MigrateDirectory scans a directory for mise/asdf config files and migrates them.

Validates Requirement: 21.4

func (*MigrationManager) MigrateFile

func (mm *MigrationManager) MigrateFile(ctx context.Context, sourcePath string, outputPath string, dryRun bool) (*MigrationReport, error)

MigrateFile converts a single mise/asdf configuration file to UniRTM format.

It auto-detects the source format from the filename.

Validates Requirements: 21.1, 21.2, 21.3, 21.6

type MigrationReport

type MigrationReport struct {
	Source            string
	Tools             []MigrationTool
	OutputFile        string
	UnsupportedFields []string
	Errors            []string
	DryRun            bool
	GeneratedAt       time.Time
}

MigrationReport summarizes the result of a migration operation.

type MigrationSource

type MigrationSource string

MigrationSource represents the source format of a migration.

const (
	// MigrationSourceMiseToml represents a .mise.toml file.
	MigrationSourceMiseToml MigrationSource = "mise.toml"
	// MigrationSourceToolVersions represents an asdf/mise .tool-versions file.
	MigrationSourceToolVersions MigrationSource = ".tool-versions"
)

type MigrationTool

type MigrationTool struct {
	Name     string
	Version  string
	Backend  string
	Provider string
	Source   MigrationSource
}

MigrationTool represents a single tool discovered during migration.

type OfflineManager

type OfflineManager struct {
	// contains filtered or unexported fields
}

OfflineManager detects network availability and provides offline operation support.

Validates Requirements: 19.1, 19.2, 19.3, 19.4, 19.5, 19.6, 19.7

func NewOfflineManager

func NewOfflineManager() *OfflineManager

NewOfflineManager creates a new OfflineManager.

func (*OfflineManager) CanOperateOffline

func (om *OfflineManager) CanOperateOffline(operation string) bool

CanOperateOffline reports whether the given operation can be performed offline.

Validates Requirements: 19.2, 19.3, 19.4, 19.5

func (*OfflineManager) IsOnline

func (om *OfflineManager) IsOnline(ctx context.Context) bool

IsOnline checks whether network connectivity is available.

It caches the result for cacheDuration to avoid repeated probes.

Validates Requirement: 19.1 (network availability detection)

func (*OfflineManager) RequireOnline

func (om *OfflineManager) RequireOnline(ctx context.Context, operation string) error

RequireOnline checks network connectivity and returns an error if offline.

Validates Requirement: 19.6 (clear feedback when network required but unavailable)

func (*OfflineManager) SkipIfOffline

func (om *OfflineManager) SkipIfOffline(ctx context.Context, operationName string) bool

SkipIfOffline logs a message and returns true if the operation should be skipped because we are offline. Used for optional network operations.

Validates Requirement: 19.7 (skip optional network operations when offline)

type OperationMetric

type OperationMetric struct {
	Tool      string
	Version   string
	Phase     OperationPhase
	StartedAt time.Time
	Duration  time.Duration
	Success   bool
}

OperationMetric records the duration of a single timed operation.

Validates Requirement: 17.1 (track operation durations)

type OperationPhase

type OperationPhase string

OperationPhase represents a timed phase of a tool operation.

const (
	PhaseDownload  OperationPhase = "download"
	PhaseExtract   OperationPhase = "extract"
	PhaseInstall   OperationPhase = "install"
	PhaseActivate  OperationPhase = "activate"
	PhaseCacheHit  OperationPhase = "cache_hit"
	PhaseCacheMiss OperationPhase = "cache_miss"
)

type OperationStatus

type OperationStatus string

OperationStatus represents the status of an operation

const (
	// StatusSuccess indicates the operation completed successfully
	StatusSuccess OperationStatus = "success"

	// StatusFailure indicates the operation failed
	StatusFailure OperationStatus = "failure"
)

type OperationType

type OperationType string

OperationType represents the type of operation being audited

const (
	// OperationInstall represents a tool installation operation
	OperationInstall OperationType = "install"

	// OperationUninstall represents a tool uninstallation operation
	OperationUninstall OperationType = "uninstall"

	// OperationActivate represents a tool activation operation
	OperationActivate OperationType = "activate"

	// OperationDeactivate represents a tool deactivation operation
	OperationDeactivate OperationType = "deactivate"

	// OperationUpdate represents a tool update operation
	OperationUpdate OperationType = "update"

	// OperationCachePurge represents a cache purge operation
	OperationCachePurge OperationType = "cache_purge"

	// OperationConfigLoad represents a configuration load operation
	OperationConfigLoad OperationType = "config_load"

	// OperationConfigUpdate represents a configuration update operation
	OperationConfigUpdate OperationType = "config_update"

	// OperationIndexUpdate represents an index update operation
	OperationIndexUpdate OperationType = "index_update"

	// OperationVersionResolve represents a version resolution operation
	OperationVersionResolve OperationType = "version_resolve"
)

type PerformanceMonitor

type PerformanceMonitor struct {
	// contains filtered or unexported fields
}

PerformanceMonitor tracks operation durations and cache hit rates.

Validates Requirements: 17.1, 17.2, 17.3, 17.4, 17.5, 17.6, 17.7

func NewPerformanceMonitor

func NewPerformanceMonitor(auditRepo repository.AuditRepository) *PerformanceMonitor

NewPerformanceMonitor creates a new PerformanceMonitor.

func (*PerformanceMonitor) AllReports

func (pm *PerformanceMonitor) AllReports() []*PerformanceReport

AllReports generates a performance report for every phase that has data.

func (*PerformanceMonitor) CacheHitRate

func (pm *PerformanceMonitor) CacheHitRate() float64

CacheHitRate returns the current cache hit rate as a percentage (0-100).

Validates Requirement: 17.2 (track cache hit rates)

func (*PerformanceMonitor) DetectRegressions

func (pm *PerformanceMonitor) DetectRegressions(threshold float64) map[OperationPhase]float64

DetectRegressions compares current p95 against baselines.

Returns a map of phase → regression percentage (positive = slower than baseline).

Validates Requirement: 17.7

func (*PerformanceMonitor) FormatReport

func (pm *PerformanceMonitor) FormatReport(r *PerformanceReport) string

FormatReport formats a PerformanceReport as a human-readable string.

func (*PerformanceMonitor) Record

func (pm *PerformanceMonitor) Record(ctx context.Context, metric OperationMetric)

Record records an operation metric.

Validates Requirements: 17.1, 17.2, 17.3, 17.4

func (*PerformanceMonitor) Report

Report generates a performance report for the given phase.

Validates Requirements: 17.5, 17.6 (percentile reports)

func (*PerformanceMonitor) SetBaseline

func (pm *PerformanceMonitor) SetBaseline(phase OperationPhase, d time.Duration)

SetBaseline sets the baseline duration for regression detection.

Validates Requirement: 17.7 (detect performance regressions)

func (*PerformanceMonitor) Track

func (pm *PerformanceMonitor) Track(ctx context.Context, tool, version string, phase OperationPhase) func(success bool)

Track is a convenience helper that times a function call and records the result.

Usage:

done := pm.Track(ctx, tool, version, PhaseDownload)
err := doDownload(...)
done(err == nil)

type PerformanceReport

type PerformanceReport struct {
	Phase   OperationPhase
	Count   int
	Average time.Duration
	P50     time.Duration
	P95     time.Duration
	P99     time.Duration
	Min     time.Duration
	Max     time.Duration
}

PerformanceReport summarizes performance metrics.

Validates Requirements: 17.5 (performance data querying), 17.6 (reports)

type PluginManager

type PluginManager struct {
	// contains filtered or unexported fields
}

PluginManager handles discovery, loading, and registration of Go plugins.

Validates Requirements: 22.1, 22.2

func NewPluginManager

func NewPluginManager(pluginsDir string, br *backend.Registry, pr *provider.Registry) *PluginManager

NewPluginManager creates a new PluginManager.

Validates Requirements: 22.1, 22.2

func (*PluginManager) Cleanup

func (pm *PluginManager) Cleanup()

Cleanup kills all the running plugin processes.

func (*PluginManager) ListLoaded

func (pm *PluginManager) ListLoaded() []PluginMetadata

ListLoaded returns metadata for all loaded plugins.

func (*PluginManager) LoadAll

func (pm *PluginManager) LoadAll(ctx context.Context) error

LoadAll discovers and loads all plugins from the plugins directory.

Validates Requirements: 22.1, 22.4 (validate compatibility), 22.5 (isolate failures)

func (*PluginManager) PluginsDir

func (pm *PluginManager) PluginsDir() string

PluginsDir returns the directory where plugins are loaded from.

type PluginMetadata

type PluginMetadata struct {
	Name       string
	Type       string // "backend" or "provider"
	APIVersion string
	Path       string
}

PluginMetadata holds information about a loaded plugin.

type ProgressReporter

type ProgressReporter func(tool string, downloaded, total int64)

ProgressReporter is a callback for concurrent download progress reporting.

type ProviderMetadata

type ProviderMetadata struct {
	Name         string
	Dependencies []Dependency
}

ProviderMetadata contains metadata about a tool provider including dependencies.

type ProviderRegistry

type ProviderRegistry interface {
	// GetProvider returns the provider for a given tool name
	GetProvider(tool string) (ProviderMetadata, error)
}

ProviderRegistry defines the interface for accessing provider metadata.

type RecoveryAction

type RecoveryAction string

RecoveryAction represents the action to take for an incomplete operation.

const (
	// RecoveryActionRetry retries the incomplete operation.
	RecoveryActionRetry RecoveryAction = "retry"
	// RecoveryActionRollback rolls back the incomplete operation.
	RecoveryActionRollback RecoveryAction = "rollback"
	// RecoveryActionIgnore ignores the incomplete operation.
	RecoveryActionIgnore RecoveryAction = "ignore"
)

type RecoveryManager

type RecoveryManager struct {
	// contains filtered or unexported fields
}

RecoveryManager detects and recovers from incomplete operations.

It checks for in-progress audit log entries and partial installation directories on startup, then offers retry/rollback/ignore options.

Validates Requirements: 3.6, 12.4, 12.5

func NewRecoveryManager

func NewRecoveryManager(
	installRepo repository.InstallationRepository,
	auditRepo repository.AuditRepository,
	installsDir string,
) *RecoveryManager

NewRecoveryManager creates a new RecoveryManager.

func (*RecoveryManager) DetectIncomplete

func (rm *RecoveryManager) DetectIncomplete(ctx context.Context) ([]IncompleteOperation, error)

DetectIncomplete scans the audit log for in-progress operations and cross-references them with the filesystem to find partial installations.

Validates Requirements: 12.4, 12.5

func (*RecoveryManager) Recover

Recover processes an incomplete operation according to the given action.

Validates Requirements: 3.6, 12.4, 12.5

func (*RecoveryManager) RecoverAll

func (rm *RecoveryManager) RecoverAll(ctx context.Context) (*RecoveryReport, error)

RecoverAll runs recovery for all detected incomplete operations using the rollback action by default.

Validates Requirements: 3.6, 12.4, 12.5

type RecoveryReport

type RecoveryReport struct {
	// Detected lists incomplete operations that were found.
	Detected []IncompleteOperation
	// Cleaned lists paths that were cleaned up.
	Cleaned []string
	// Errors lists errors encountered during recovery.
	Errors []string
}

RecoveryReport summarizes the recovery process.

type SearchOptions

type SearchOptions struct {
	// Query is the search query string (matches name, description, tags)
	Query string
	// Backend filters results by backend type (empty = all backends)
	Backend string
	// Limit limits the number of results (0 = no limit)
	Limit int
	// Offset skips the first N results (for pagination)
	Offset int
}

SearchOptions defines options for searching the tool index

type SecurityManager

type SecurityManager struct {
	// contains filtered or unexported fields
}

SecurityManager handles checksum verification, signature checking, and security audit logging for installed tools.

Validates Requirements: 20.3, 20.4, 20.5, 20.6, 20.7

func NewSecurityManager

func NewSecurityManager(auditRepo repository.AuditRepository) *SecurityManager

NewSecurityManager creates a new SecurityManager.

func (*SecurityManager) VerifyChecksum

func (sm *SecurityManager) VerifyChecksum(ctx context.Context, tool, version, filePath, expected string) (*SecurityVerificationResult, error)

VerifyChecksum verifies the checksum of a downloaded file.

Validates Requirements: 20.3 (checksum verification), 20.5 (store checksum in database)

func (*SecurityManager) VerifyInstallation

func (sm *SecurityManager) VerifyInstallation(ctx context.Context, installation *repository.Installation, binaryPath string) (*SecurityVerificationResult, error)

VerifyInstallation verifies that an installed tool's checksum matches the stored value.

Validates Requirement: 20.5 (checksum storage and re-verification)

type SecurityVerificationResult

type SecurityVerificationResult struct {
	Tool      string
	Version   string
	FilePath  string
	Algorithm ChecksumAlgorithm
	Expected  string
	Actual    string
	Passed    bool
	Warning   string
}

SecurityVerificationResult represents the result of a security check.

type ShellConfigManager

type ShellConfigManager struct {
	// contains filtered or unexported fields
}

ShellConfigManager handles persistent configuration changes in shell RC files.

func NewShellConfigManager

func NewShellConfigManager(formatter output.Formatter, dryRun bool) *ShellConfigManager

NewShellConfigManager creates a new ShellConfigManager.

func (*ShellConfigManager) GetConfigPath

func (m *ShellConfigManager) GetConfigPath(shell ShellType) (string, error)

GetConfigPath returns the standard configuration file path for the given shell.

func (*ShellConfigManager) Inject

func (m *ShellConfigManager) Inject(shell ShellType, marker string, content string) error

Inject appends or updates a configuration block in the shell RC file.

func (*ShellConfigManager) Remove

func (m *ShellConfigManager) Remove(shell ShellType, marker string) error

Remove removes configuration lines related to the given marker from the shell RC file.

type ShellType

type ShellType string

ShellType represents the type of shell for activation scripts.

const (
	// ShellBash represents bash shell
	ShellBash ShellType = "bash"
	// ShellZsh represents zsh shell
	ShellZsh ShellType = "zsh"
	// ShellFish represents fish shell
	ShellFish ShellType = "fish"
	// ShellPowerShell represents PowerShell
	ShellPowerShell ShellType = "powershell"
)

func DetectShell

func DetectShell() (ShellType, error)

DetectShell detects the current shell from the environment.

It checks the SHELL environment variable and returns the corresponding ShellType. If the shell cannot be detected, it returns a sensible default for the platform.

Example

ExampleDetectShell demonstrates detecting the current shell.

package main

import (
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/service"
)

func main() {
	shell, err := service.DetectShell()
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Detected shell type: %s\n", shell)
	fmt.Println("Shell is supported:",
		shell == service.ShellBash ||
			shell == service.ShellZsh ||
			shell == service.ShellFish ||
			shell == service.ShellPowerShell)

	// Output will vary by platform, but should always be supported
	// Example output on Linux:
	// Detected shell type: bash
	// Shell is supported: true
}

type ToolInstallRequest

type ToolInstallRequest struct {
	ToolKey string // The full key as specified in the configuration
	Tool    string // The stripped tool name
	Version string
	Backend string
	// DependsOn lists tool names that must be installed before this one.
	DependsOn []string
}

ToolInstallRequest specifies a tool and version to install.

type ToolMetadata

type ToolMetadata struct {
	// AvailableVersions is a list of available versions for the tool
	AvailableVersions []string `json:"available_versions,omitempty"`
	// Tags are searchable tags for the tool
	Tags []string `json:"tags,omitempty"`
	// ReleaseDate is the date of the latest release
	ReleaseDate string `json:"release_date,omitempty"`
	// Stars is the number of GitHub stars (if applicable)
	Stars int `json:"stars,omitempty"`
	// LastUpdated is when this metadata was last updated
	LastUpdated time.Time `json:"last_updated"`
}

ToolMetadata represents extended metadata for a tool in the index

type ToolSpec

type ToolSpec struct {
	Name         string
	Version      string
	BackendName  string
	OriginalName string
}

ToolSpec describes a single tool entry from project config.

type ToolToInstall

type ToolToInstall struct {
	OriginalName string
	ToolName     string
	BackendName  string
	Version      string
	Config       config.ToolConfig
}

ToolToInstall represents a tool with its resolved backend information for sorting.

type UpdateInfo

type UpdateInfo struct {
	Tool           string // Tool name
	CurrentVersion string // Currently installed version
	LatestVersion  string // Latest available version
	Backend        string // Backend used for the tool
	UpdateRequired bool   // Whether an update is available
}

UpdateInfo represents information about an available update for a tool.

type UpdateManager

type UpdateManager struct {
	// contains filtered or unexported fields
}

UpdateManager manages tool updates with version checking, preview, and rollback support.

Validates Requirements: 25.1, 25.2, 25.3, 25.4, 25.5, 25.6, 25.7

func NewUpdateManager

func NewUpdateManager(
	backendRegistry *backend.Registry,
	providerRegistry *provider.Registry,
	downloadManager *download.Manager,
	installRepo repository.InstallationRepository,
	auditRepo repository.AuditRepository,
	txManager transaction.TransactionManager,
	configManager *config.Config,
) *UpdateManager

NewUpdateManager creates a new update manager.

func (*UpdateManager) CheckForUpdates

func (um *UpdateManager) CheckForUpdates(ctx context.Context) ([]UpdateInfo, error)

CheckForUpdates checks for newer versions of installed tools.

Validates Requirement 25.1: Check for newer versions of installed tools

func (*UpdateManager) DisableAutomaticUpdates

func (um *UpdateManager) DisableAutomaticUpdates(ctx context.Context) error

DisableAutomaticUpdates disables automatic updates.

func (*UpdateManager) EnableAutomaticUpdates

func (um *UpdateManager) EnableAutomaticUpdates(ctx context.Context, schedule string) error

EnableAutomaticUpdates enables automatic updates with the specified schedule.

Validates Requirement 25.6: Support automatic updates (opt-in, configurable schedule)

Note: This method stores the automatic update configuration. The actual scheduling and execution would be handled by a separate scheduler service or cron job that calls UpdateAll periodically.

func (*UpdateManager) PreviewUpdates

func (um *UpdateManager) PreviewUpdates(ctx context.Context) (*UpdatePreview, error)

PreviewUpdates shows a preview of what will be updated before applying.

Validates Requirement 25.5: Show a preview of what will be updated before applying

func (*UpdateManager) UpdateAll

func (um *UpdateManager) UpdateAll(ctx context.Context) ([]UpdateResult, error)

UpdateAll updates all tools to their latest versions.

Validates Requirement 25.3: Support updating all tools to their latest versions Validates Requirement 25.4: Respect version constraints in configuration files

func (*UpdateManager) UpdateTool

func (um *UpdateManager) UpdateTool(ctx context.Context, tool, oldVersion, targetVersion string) (*UpdateResult, error)

UpdateTool updates a specific tool to a specific version.

Validates Requirement 25.2: Support updating a specific tool to a specific version Validates Requirement 25.7: Rollback to the previous version when an update fails

type UpdatePreview

type UpdatePreview struct {
	Updates        []UpdateInfo  // List of tools that will be updated
	TotalUpdates   int           // Total number of updates
	TotalInstalled int           // Total number of installed tools checked
	EstimatedTime  time.Duration // Estimated time for all updates
}

UpdatePreview represents a preview of what will be updated.

type UpdateResult

type UpdateResult struct {
	Tool           string        // Tool name
	OldVersion     string        // Previous version
	NewVersion     string        // New version after update
	Success        bool          // Whether the update succeeded
	Error          string        // Error message if update failed
	Duration       time.Duration // Time taken for the update
	RolledBack     bool          // Whether a rollback was performed
	RollbackReason string        // Reason for rollback if applicable
}

UpdateResult represents the result of an update operation.

type ValidationIssue

type ValidationIssue struct {
	Severity ValidationSeverity
	Field    string
	Message  string
}

ValidationIssue represents a single configuration validation problem.

type ValidationSeverity

type ValidationSeverity string

ValidationSeverity represents the severity of a validation issue.

const (
	SeverityError   ValidationSeverity = "error"
	SeverityWarning ValidationSeverity = "warning"
)

type VersionManager

type VersionManager struct {
	// contains filtered or unexported fields
}

VersionManager handles version constraint parsing and resolution. It enforces explicit version requirements and delegates to backends for resolution.

func NewVersionManager

func NewVersionManager(backends map[string]backend.Backend) *VersionManager

NewVersionManager creates a new VersionManager with the given backends.

func (*VersionManager) ListAvailableVersions

func (vm *VersionManager) ListAvailableVersions(ctx context.Context, backendName, tool string, platform backend.Platform) ([]backend.VersionInfo, error)

ListAvailableVersions lists all available versions for a tool from the specified backend. This is useful for displaying available versions to users.

Returns a list of VersionInfo objects in descending order (newest first).

func (*VersionManager) ParseVersionConstraint

func (vm *VersionManager) ParseVersionConstraint(versionStr string) (*version.Version, error)

ParseVersionConstraint parses a version constraint string into a Version object. This is a convenience wrapper around ParseVersion that enforces explicit version requirements.

Returns an error if: - The version string is empty (explicit version required) - The version string is invalid

func (*VersionManager) ResolveVersion

func (vm *VersionManager) ResolveVersion(ctx context.Context, backendName, tool, versionSpec string, platform backend.Platform) (*backend.VersionInfo, error)

ResolveVersion resolves a version specification to a concrete version using the specified backend.

Parameters: - ctx: Context for cancellation and timeouts - backendName: Name of the backend to use for resolution (e.g., "github", "aqua", "http") - tool: Name of the tool to resolve the version for - versionSpec: Version specification (exact version, range, or alias) - platform: Target platform for the resolution

Returns: - The resolved VersionInfo with concrete version and download information - An error if resolution fails

Resolution behavior: - Exact versions (e.g., "1.20.0"): Delegates to backend.GetDownloadInfo - Aliases (e.g., "latest", "lts", "stable"): Delegates to backend.ResolveVersion - Ranges (e.g., ">=1.20.0", "^3.11", "~2.7.0"): Delegates to backend.ResolveVersion

func (*VersionManager) SupportsChecksum

func (vm *VersionManager) SupportsChecksum(backendName string) (bool, error)

SupportsChecksum checks if the specified backend supports checksum verification.

func (*VersionManager) SupportsGPG

func (vm *VersionManager) SupportsGPG(backendName string) (bool, error)

SupportsGPG checks if the specified backend supports GPG signature verification.

func (*VersionManager) ValidateVersionConstraint

func (vm *VersionManager) ValidateVersionConstraint(versionStr string) error

ValidateVersionConstraint validates a version constraint string without resolving it. This is useful for configuration validation.

Returns an error if the version constraint is invalid.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL