[agent/claude] Update all Go files that import forge.lthn.ai/core/go or for... #2
4 changed files with 75 additions and 53 deletions
3
go.mod
3
go.mod
|
|
@ -3,8 +3,8 @@ module forge.lthn.ai/core/go-io
|
|||
go 1.26.0
|
||||
|
||||
require (
|
||||
dappco.re/go/core v0.4.7
|
||||
forge.lthn.ai/Snider/Borg v0.3.1
|
||||
forge.lthn.ai/core/go v0.3.3
|
||||
forge.lthn.ai/core/go-crypt v0.1.6
|
||||
forge.lthn.ai/core/go-log v0.0.4
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.4
|
||||
|
|
@ -15,6 +15,7 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
forge.lthn.ai/core/go v0.3.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.4.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect
|
||||
|
|
|
|||
6
go.sum
6
go.sum
|
|
@ -1,7 +1,9 @@
|
|||
dappco.re/go/core v0.4.7 h1:KmIA/2lo6rl1NMtLrKqCWfMlUqpDZYH3q0/d10dTtGA=
|
||||
dappco.re/go/core v0.4.7/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A=
|
||||
forge.lthn.ai/Snider/Borg v0.3.1 h1:gfC1ZTpLoZai07oOWJiVeQ8+qJYK8A795tgVGJHbVL8=
|
||||
forge.lthn.ai/Snider/Borg v0.3.1/go.mod h1:Z7DJD0yHXsxSyM7Mjl6/g4gH1NBsIz44Bf5AFlV76Wg=
|
||||
forge.lthn.ai/core/go v0.3.3 h1:kYYZ2nRYy0/Be3cyuLJspRjLqTMxpckVyhb/7Sw2gd0=
|
||||
forge.lthn.ai/core/go v0.3.3/go.mod h1:Cp4ac25pghvO2iqOu59t1GyngTKVOzKB5/VPdhRi9CQ=
|
||||
forge.lthn.ai/core/go v0.3.0 h1:mOG97ApMprwx9Ked62FdWVwXTGSF6JO6m0DrVpoH2Q4=
|
||||
forge.lthn.ai/core/go v0.3.0/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc=
|
||||
forge.lthn.ai/core/go-crypt v0.1.6 h1:jB7L/28S1NR+91u3GcOYuKfBLzPhhBUY1fRe6WkGVns=
|
||||
forge.lthn.ai/core/go-crypt v0.1.6/go.mod h1:4VZAGqxlbadhSB66sJkdj54/HSJ+bSxVgwWK5kMMYDo=
|
||||
forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0=
|
||||
|
|
|
|||
|
|
@ -7,14 +7,29 @@ import (
|
|||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "forge.lthn.ai/core/go-log"
|
||||
core "forge.lthn.ai/core/go/pkg/core"
|
||||
|
||||
"forge.lthn.ai/core/go-io"
|
||||
)
|
||||
|
||||
// Service implements the core.Workspace interface.
|
||||
// Workspace provides management for encrypted user workspaces.
|
||||
type Workspace interface {
|
||||
CreateWorkspace(identifier, password string) (string, error)
|
||||
SwitchWorkspace(name string) error
|
||||
WorkspaceFileGet(filename string) (string, error)
|
||||
WorkspaceFileSet(filename, content string) error
|
||||
}
|
||||
|
||||
// cryptProvider is the interface for PGP key generation.
|
||||
type cryptProvider interface {
|
||||
CreateKeyPair(name, passphrase string) (string, error)
|
||||
}
|
||||
|
||||
// Service implements the Workspace interface.
|
||||
type Service struct {
|
||||
core *core.Core
|
||||
crypt cryptProvider
|
||||
activeWorkspace string
|
||||
rootPath string
|
||||
medium io.Medium
|
||||
|
|
@ -22,7 +37,8 @@ type Service struct {
|
|||
}
|
||||
|
||||
// New creates a new Workspace service instance.
|
||||
func New(c *core.Core) (any, error) {
|
||||
// An optional cryptProvider can be passed to supply PGP key generation.
|
||||
func New(c *core.Core, crypt ...cryptProvider) (any, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, coreerr.E("workspace.New", "failed to determine home directory", err)
|
||||
|
|
@ -35,6 +51,10 @@ func New(c *core.Core) (any, error) {
|
|||
medium: io.Local,
|
||||
}
|
||||
|
||||
if len(crypt) > 0 && crypt[0] != nil {
|
||||
s.crypt = crypt[0]
|
||||
}
|
||||
|
||||
if err := s.medium.EnsureDir(rootPath); err != nil {
|
||||
return nil, coreerr.E("workspace.New", "failed to ensure root directory", err)
|
||||
}
|
||||
|
|
@ -43,13 +63,16 @@ func New(c *core.Core) (any, error) {
|
|||
}
|
||||
|
||||
// CreateWorkspace creates a new encrypted workspace.
|
||||
// Identifier is hashed (SHA-256 as proxy for LTHN) to create the directory name.
|
||||
// Identifier is hashed (SHA-256) to create the directory name.
|
||||
// A PGP keypair is generated using the password.
|
||||
func (s *Service) CreateWorkspace(identifier, password string) (string, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// 1. Identification (LTHN hash proxy)
|
||||
if s.crypt == nil {
|
||||
return "", coreerr.E("workspace.CreateWorkspace", "crypt service not available", nil)
|
||||
}
|
||||
|
||||
hash := sha256.Sum256([]byte(identifier))
|
||||
wsID := hex.EncodeToString(hash[:])
|
||||
wsPath := filepath.Join(s.rootPath, wsID)
|
||||
|
|
@ -58,26 +81,18 @@ func (s *Service) CreateWorkspace(identifier, password string) (string, error) {
|
|||
return "", coreerr.E("workspace.CreateWorkspace", "workspace already exists", nil)
|
||||
}
|
||||
|
||||
// 2. Directory structure
|
||||
dirs := []string{"config", "log", "data", "files", "keys"}
|
||||
for _, d := range dirs {
|
||||
for _, d := range []string{"config", "log", "data", "files", "keys"} {
|
||||
if err := s.medium.EnsureDir(filepath.Join(wsPath, d)); err != nil {
|
||||
return "", coreerr.E("workspace.CreateWorkspace", "failed to create directory: "+d, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. PGP Keypair generation
|
||||
crypt := s.core.Crypt()
|
||||
if crypt == nil {
|
||||
return "", coreerr.E("workspace.CreateWorkspace", "crypt service not available", nil)
|
||||
}
|
||||
privKey, err := crypt.CreateKeyPair(identifier, password)
|
||||
privKey, err := s.crypt.CreateKeyPair(identifier, password)
|
||||
if err != nil {
|
||||
return "", coreerr.E("workspace.CreateWorkspace", "failed to generate keys", err)
|
||||
}
|
||||
|
||||
// Save private key
|
||||
if err := s.medium.Write(filepath.Join(wsPath, "keys", "private.key"), privKey); err != nil {
|
||||
if err := s.medium.WriteMode(filepath.Join(wsPath, "keys", "private.key"), privKey, 0600); err != nil {
|
||||
return "", coreerr.E("workspace.CreateWorkspace", "failed to save private key", err)
|
||||
}
|
||||
|
||||
|
|
@ -98,36 +113,41 @@ func (s *Service) SwitchWorkspace(name string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// activeFilePath returns the full path to a file in the active workspace,
|
||||
// or an error if no workspace is active.
|
||||
func (s *Service) activeFilePath(op, filename string) (string, error) {
|
||||
if s.activeWorkspace == "" {
|
||||
return "", coreerr.E(op, "no active workspace", nil)
|
||||
}
|
||||
return filepath.Join(s.rootPath, s.activeWorkspace, "files", filename), nil
|
||||
}
|
||||
|
||||
// WorkspaceFileGet retrieves the content of a file from the active workspace.
|
||||
// In a full implementation, this would involve decryption using the workspace key.
|
||||
func (s *Service) WorkspaceFileGet(filename string) (string, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
if s.activeWorkspace == "" {
|
||||
return "", coreerr.E("workspace.WorkspaceFileGet", "no active workspace", nil)
|
||||
path, err := s.activeFilePath("workspace.WorkspaceFileGet", filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
path := filepath.Join(s.rootPath, s.activeWorkspace, "files", filename)
|
||||
return s.medium.Read(path)
|
||||
}
|
||||
|
||||
// WorkspaceFileSet saves content to a file in the active workspace.
|
||||
// In a full implementation, this would involve encryption using the workspace key.
|
||||
func (s *Service) WorkspaceFileSet(filename, content string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.activeWorkspace == "" {
|
||||
return coreerr.E("workspace.WorkspaceFileSet", "no active workspace", nil)
|
||||
path, err := s.activeFilePath("workspace.WorkspaceFileSet", filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := filepath.Join(s.rootPath, s.activeWorkspace, "files", filename)
|
||||
return s.medium.Write(path, content)
|
||||
}
|
||||
|
||||
// HandleIPCEvents handles workspace-related IPC messages.
|
||||
func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error {
|
||||
func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result {
|
||||
switch m := msg.(type) {
|
||||
case map[string]any:
|
||||
action, _ := m["action"].(string)
|
||||
|
|
@ -135,15 +155,21 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error {
|
|||
case "workspace.create":
|
||||
id, _ := m["identifier"].(string)
|
||||
pass, _ := m["password"].(string)
|
||||
_, err := s.CreateWorkspace(id, pass)
|
||||
return err
|
||||
wsID, err := s.CreateWorkspace(id, pass)
|
||||
if err != nil {
|
||||
return core.Result{}
|
||||
}
|
||||
return core.Result{Value: wsID, OK: true}
|
||||
case "workspace.switch":
|
||||
name, _ := m["name"].(string)
|
||||
return s.SwitchWorkspace(name)
|
||||
if err := s.SwitchWorkspace(name); err != nil {
|
||||
return core.Result{}
|
||||
}
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// Ensure Service implements core.Workspace.
|
||||
var _ core.Workspace = (*Service)(nil)
|
||||
// Ensure Service implements Workspace.
|
||||
var _ Workspace = (*Service)(nil)
|
||||
|
|
|
|||
|
|
@ -1,32 +1,25 @@
|
|||
package workspace
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"forge.lthn.ai/core/go-crypt/crypt/openpgp"
|
||||
core "forge.lthn.ai/core/go/pkg/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWorkspace(t *testing.T) {
|
||||
// Setup core with crypt service
|
||||
c, _ := core.New(
|
||||
core.WithName("crypt", openpgp.New),
|
||||
)
|
||||
|
||||
tempHome, _ := os.MkdirTemp("", "core-test-home")
|
||||
defer os.RemoveAll(tempHome)
|
||||
|
||||
// Mock os.UserHomeDir by setting HOME env
|
||||
oldHome := os.Getenv("HOME")
|
||||
os.Setenv("HOME", tempHome)
|
||||
defer os.Setenv("HOME", oldHome)
|
||||
|
||||
s_any, err := New(c)
|
||||
c := core.New()
|
||||
pgpSvc, err := openpgp.New(nil)
|
||||
assert.NoError(t, err)
|
||||
s := s_any.(*Service)
|
||||
|
||||
tempHome := t.TempDir()
|
||||
t.Setenv("HOME", tempHome)
|
||||
|
||||
svc, err := New(c, pgpSvc.(cryptProvider))
|
||||
assert.NoError(t, err)
|
||||
s := svc.(*Service)
|
||||
|
||||
// Test CreateWorkspace
|
||||
id, err := s.CreateWorkspace("test-user", "pass123")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue