-
Notifications
You must be signed in to change notification settings - Fork 0
API Reference
This page documents all 60+ host service operations available to plugins. All operations use structured logging with request/completion tracking, client identification, and microsecond-precision metrics.
The host service API follows two consistent patterns:
All unary operations and methods that establish file handles use a rootDir + path pattern:
// Pattern: operation(ctx, rootDir, path, ...other parameters)
data, err := hostServiceClient.ReadFile(ctx, rootDir, path)
err = hostServiceClient.WriteFile(ctx, rootDir, path, data, perm)
handle, size, err := hostServiceClient.FileOpen(ctx, rootDir, path, flags, perm)Requirements:
-
rootDir: MUST be an absolute path (e.g.,
/home/user,/var/data) - path: CAN be absolute or relative, but MUST NOT escape the rootDir
Why this pattern?
- Security: Confines operations within a specific root directory, preventing path traversal attacks
- Clarity: Explicitly separates the security boundary (rootDir) from the target location (path)
- Flexibility: Plugins can pass absolute or relative paths while the host enforces confinement
Once a file handle is established, all subsequent operations use only the handle:
// First, establish the resource and get a handle
handle, size, err := hostServiceClient.FileOpen(ctx, rootDir, path, flags, perm)
// Then, operate using only the handle - no paths needed
reader, err := hostServiceClient.FileReader(ctx, handle, chunkSize)
offset, err := hostServiceClient.FileSeek(ctx, handle, offset, whence)
info, err := hostServiceClient.FileStat(ctx, handle)
err = hostServiceClient.FileClose(ctx, handle)Why this pattern?
- Performance: Path resolution and security checks happen once at open time
- Stateful operations: Supports file position (seek), progressive reading/writing
- Resource management: Explicit lifecycle with open/close
- Familiar: Mirrors standard file I/O patterns
| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
ReadDir |
(ctx, rootDir, path) |
os.ReadDir |
Read directory contents |
Mkdir |
(ctx, rootDir, name, perm) |
os.Mkdir |
Create single directory |
MkdirAll |
(ctx, rootDir, path, perm) |
os.MkdirAll |
Create directory with parents |
MkdirTemp |
(ctx, rootDir, pattern) |
os.MkdirTemp |
Create temporary directory (auto-cleanup) |
Example: ReadDir
entries, err := hostServiceClient.ReadDir(ctx, "/home/user", "documents")
if err != nil {
return err
}
for _, entry := range entries {
fmt.Printf("%s (dir: %v)\n", entry.Name(), entry.IsDir())
}Example: MkdirTemp
// Create temp directory - automatically cleaned up when plugin disconnects
tempDir, err := hostServiceClient.MkdirTemp(ctx, "", "myapp-*")
if err != nil {
return err
}
fmt.Printf("Created temp dir: %s\n", tempDir)
// No cleanup needed - host handles it!| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
ReadFile |
(ctx, rootDir, path) |
os.ReadFile |
Read entire file contents |
WriteFile |
(ctx, rootDir, path, data, perm) |
os.WriteFile |
Write entire file contents |
Example: ReadFile
data, err := hostServiceClient.ReadFile(ctx, "/home/user", "config.json")
if err != nil {
return err
}
// Parse data
var config Config
json.Unmarshal(data, &config)Example: WriteFile
data := []byte("Hello, World!")
err := hostServiceClient.WriteFile(ctx, "/home/user", "output.txt", data, 0644)
if err != nil {
return err
}| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
Stat |
(ctx, rootDir, path) |
os.Stat |
Get file/directory info |
Lstat |
(ctx, rootDir, path) |
os.Lstat |
Get file info without following symlinks |
Example: Stat
fileInfo, err := hostServiceClient.Stat(ctx, "/home/user", "data.txt")
if err != nil {
return err
}
fmt.Printf("Size: %d, Mode: %v, ModTime: %v\n",
fileInfo.Size(), fileInfo.Mode(), fileInfo.ModTime())| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
Rename |
(ctx, rootDir, oldPath, newPath) |
os.Rename |
Rename or move file/directory |
Remove |
(ctx, rootDir, path) |
os.Remove |
Delete file or empty directory |
RemoveAll |
(ctx, rootDir, path) |
os.RemoveAll |
Recursively delete directory and contents |
Example: Rename
err := hostServiceClient.Rename(ctx, "/home/user", "old-name.txt", "new-name.txt")
if err != nil {
return err
}Example: RemoveAll
// ⚠️ USE WITH EXTREME CAUTION - deletes directory and ALL contents
err := hostServiceClient.RemoveAll(ctx, "/home/user", "old-project")
if err != nil {
return err
}| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
Symlink |
(ctx, rootDir, oldPath, newPath) |
os.Symlink |
Create symbolic link |
Link |
(ctx, rootDir, oldPath, newPath) |
os.Link |
Create hard link |
Readlink |
(ctx, rootDir, path) |
os.Readlink |
Read symbolic link target |
Example: Creating and Reading Symlinks
// Create symbolic link
err := hostServiceClient.Symlink(ctx, "/home/user", "original.txt", "link.txt")
if err != nil {
return err
}
// Read where the link points
target, err := hostServiceClient.Readlink(ctx, "/home/user", "link.txt")
if err != nil {
return err
}
fmt.Printf("Link points to: %s\n", target)
// Get info about the link itself (not the target)
linkInfo, err := hostServiceClient.Lstat(ctx, "/home/user", "link.txt")| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
Chmod |
(ctx, rootDir, path, mode) |
os.Chmod |
Change file permissions |
Chown |
(ctx, rootDir, path, uid, gid) |
os.Chown |
Change file ownership |
Lchown |
(ctx, rootDir, path, uid, gid) |
os.Lchown |
Change ownership without following symlinks |
Chtimes |
(ctx, rootDir, path, atime, mtime) |
os.Chtimes |
Change access/modification times |
Example: Chmod
// Make script executable
err := hostServiceClient.Chmod(ctx, "/home/user", "script.sh", 0755)
if err != nil {
return err
}Example: Chtimes
atime := time.Now()
mtime := time.Now().Add(-24 * time.Hour)
err := hostServiceClient.Chtimes(ctx, "/home/user", "file.txt", atime, mtime)| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
FileOpen |
(ctx, rootDir, path, flag, perm) |
os.OpenFile |
Open file and return handle with size |
FileCreate |
(ctx, rootDir, path) |
os.Create |
Create or truncate file |
FileCreateTemp |
(ctx, rootDir, pattern) |
os.CreateTemp |
Create temporary file (auto-cleanup) |
FileClose |
(ctx, handle) |
(*os.File).Close |
Close file handle |
Example: FileOpen
// Open file for reading
handle, size, err := hostServiceClient.FileOpen(ctx, "/home/user", "data.bin", os.O_RDONLY, 0)
if err != nil {
return err
}
defer hostServiceClient.FileClose(ctx, handle)
fmt.Printf("Opened file with handle: %s, size: %d\n", handle, size)Example: FileCreateTemp
// Create temporary file - automatically cleaned up
handle, err := hostServiceClient.FileCreateTemp(ctx, "", "work-*.tmp")
if err != nil {
return err
}
defer hostServiceClient.FileClose(ctx, handle)
// Use the file...| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
FileStat |
(ctx, handle) |
(*os.File).Stat |
Get file info for open handle |
FileSeek |
(ctx, handle, offset, whence) |
(*os.File).Seek |
Seek to position in file |
FileSync |
(ctx, handle) |
(*os.File).Sync |
Flush buffered data to disk |
FileTruncate |
(ctx, handle, size) |
(*os.File).Truncate |
Resize file to specified size |
FileChmod |
(ctx, handle, mode) |
(*os.File).Chmod |
Change permissions on open file |
FileChown |
(ctx, handle, uid, gid) |
(*os.File).Chown |
Change ownership on open file |
Example: FileSeek
// Seek to position 1024 from start
newOffset, err := hostServiceClient.FileSeek(ctx, handle, 1024, 0)
if err != nil {
return err
}
fmt.Printf("New position: %d\n", newOffset)Example: FileTruncate
// Resize file to 1024 bytes
err := hostServiceClient.FileTruncate(ctx, handle, 1024)
if err != nil {
return err
}
// Ensure changes are written to disk
err = hostServiceClient.FileSync(ctx, handle)| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
FileReader |
(ctx, handle, chunkSize) |
io.Reader |
Stream file contents in chunks (8KB-3.81MB) |
FileWriter |
(ctx, handle) |
io.WriteCloser |
Stream write to file |
FileReadSection |
(ctx, handle, offset, size) |
(*os.File).ReadAt |
Read specific section of file at offset |
FileWriteSection |
(ctx, handle, offset, maxLen, data) |
(*os.File).WriteAt |
Write data to specific section at offset |
Example: FileReader (Streaming)
// Open file
handle, _, err := hostServiceClient.FileOpen(ctx, "/home/user", "large-file.bin", os.O_RDONLY, 0)
if err != nil {
return err
}
defer hostServiceClient.FileClose(ctx, handle)
// Stream read with 64KB chunks
chunkSize := uint32(64 * 1024)
stream, err := hostServiceClient.FileReader(ctx, handle, chunkSize)
if err != nil {
return err
}
// Read chunks
buffer := make([]byte, 4096)
for {
n, err := stream.Read(buffer)
if n > 0 {
// Process buffer[:n]
fmt.Printf("Read %d bytes\n", n)
}
if err == io.EOF {
break
}
if err != nil {
return err
}
}Chunk Size Guidelines:
- Minimum: 8KB (8,192 bytes) - Enforced minimum
- Maximum: ~3.81MB (4,000,000 bytes) - gRPC message limit
- Recommended: 64KB-1MB - Best balance for most use cases
Example: FileWriter (Streaming)
// Open file for writing
handle, _, err := hostServiceClient.FileOpen(ctx, "/home/user", "output.bin", os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
defer hostServiceClient.FileClose(ctx, handle)
// Get writer
writer, err := hostServiceClient.FileWriter(ctx, handle)
if err != nil {
return err
}
// Write data in chunks
data := []byte("large amount of data...")
chunkSize := 64 * 1024
for i := 0; i < len(data); i += chunkSize {
end := i + chunkSize
if end > len(data) {
end = len(data)
}
n, err := writer.Write(data[i:end])
if err != nil {
return err
}
fmt.Printf("Wrote %d bytes\n", n)
}
// Finish writing
err = writer.Close()Example: FileReadSection (Random Access)
// Read 100 bytes starting at offset 4096
offset := int64(4096)
size := int32(100)
data, err := hostServiceClient.FileReadSection(ctx, handle, offset, size)
if err != nil {
return err
}
fmt.Printf("Read %d bytes from offset %d: %s\n", len(data), offset, string(data))Example: FileWriteSection (Random Access)
// Write data at specific offset
offset := int64(1024)
maxLength := int32(50)
data := []byte("Update at offset")
bytesWritten, err := hostServiceClient.FileWriteSection(ctx, handle, offset, maxLength, data)
if err != nil {
return err
}
fmt.Printf("Wrote %d bytes at offset %d\n", bytesWritten, offset)
// Ensure changes are synced
err = hostServiceClient.FileSync(ctx, handle)Use Cases for Section Operations:
- Random access I/O without seeking
- Patch updates to binary files
- Header manipulation at known offsets
- Database-like operations with fixed positions
- Concurrent access to different file sections
| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
GetEnv |
(ctx, key) |
os.Getenv |
Get environment variable |
Example:
home, err := hostServiceClient.GetEnv(ctx, "HOME")
if err != nil {
return err
}
fmt.Printf("Home directory: %s\n", home)| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
TempDir |
(ctx) |
os.TempDir |
Get system temp directory |
UserCacheDir |
(ctx) |
os.UserCacheDir |
Get user cache directory |
UserConfigDir |
(ctx) |
os.UserConfigDir |
Get user config directory |
UserHomeDir |
(ctx) |
os.UserHomeDir |
Get user home directory |
Example:
// Get various standard directories
tempDir, _ := hostServiceClient.TempDir(ctx)
cacheDir, _ := hostServiceClient.UserCacheDir(ctx)
configDir, _ := hostServiceClient.UserConfigDir(ctx)
homeDir, _ := hostServiceClient.UserHomeDir(ctx)
fmt.Printf("Temp: %s\n", tempDir)
fmt.Printf("Cache: %s\n", cacheDir)
fmt.Printf("Config: %s\n", configDir)
fmt.Printf("Home: %s\n", homeDir)| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
Getuid |
(ctx) |
os.Getuid |
Get real user ID |
Getgid |
(ctx) |
os.Getgid |
Get real group ID |
Geteuid |
(ctx) |
os.Geteuid |
Get effective user ID |
Getegid |
(ctx) |
os.Getegid |
Get effective group ID |
GetGroups |
(ctx) |
os.Getgroups |
Get supplementary group IDs |
Example:
uid, _ := hostServiceClient.Getuid(ctx)
gid, _ := hostServiceClient.Getgid(ctx)
euid, _ := hostServiceClient.Geteuid(ctx)
egid, _ := hostServiceClient.Getegid(ctx)
groups, _ := hostServiceClient.GetGroups(ctx)
fmt.Printf("UID: %d, GID: %d\n", uid, gid)
fmt.Printf("Effective UID: %d, GID: %d\n", euid, egid)
fmt.Printf("Groups: %v\n", groups)| Operation | Signature | stdlib Equivalent | Description |
|---|---|---|---|
Getpid |
(ctx) |
os.Getpid |
Get current process ID |
Getppid |
(ctx) |
os.Getppid |
Get parent process ID |
Example:
pid, _ := hostServiceClient.Getpid(ctx)
ppid, _ := hostServiceClient.Getppid(ctx)
fmt.Printf("Process ID: %d\n", pid)
fmt.Printf("Parent Process ID: %d\n", ppid)Unlike standard library temporary files, this implementation provides truly temporary resources with server-side lifecycle management. When a plugin connection closes, all temporary resources are automatically cleaned up.
Key Benefits:
- No resource leaks from crashed plugins
- Simplified plugin code - no manual cleanup
- Secure isolation - each plugin's temps are tracked separately
- Better testing - no leftover files
Example: Complete Temp File Lifecycle
// Create temp directory
tempDir, err := hostServiceClient.MkdirTemp(ctx, "", "myapp-*")
if err != nil {
return err
}
fmt.Printf("Created temp dir: %s\n", tempDir)
// Create temp file
handle, err := hostServiceClient.FileCreateTemp(ctx, tempDir, "data-*.json")
if err != nil {
return err
}
// Write to temp file
writer, _ := hostServiceClient.FileWriter(ctx, handle)
writer.Write([]byte(`{"status": "processing"}`))
writer.Close()
// Close handle
hostServiceClient.FileClose(ctx, handle)
// NO CLEANUP NEEDED!
// When plugin disconnects, both the temp file and directory are automatically removedFor small files, use unary operations:
// Read
data, err := hostServiceClient.ReadFile(ctx, "/home/user", "input.txt")
// Write
err = hostServiceClient.WriteFile(ctx, "/home/user", "output.txt", data, 0644)For large files, use handle-based streaming:
// Open
handle, size, err := hostServiceClient.FileOpen(ctx, "/home/user", "large.bin", os.O_RDONLY, 0)
defer hostServiceClient.FileClose(ctx, handle)
// Stream read
stream, _ := hostServiceClient.FileReader(ctx, handle, 64*1024)
buffer := make([]byte, 4096)
for {
n, err := stream.Read(buffer)
if n > 0 {
// Process buffer[:n]
}
if err == io.EOF {
break
}
}// List all files in a directory (from filelister plugin)
entries, err := hostServiceClient.ReadDir(ctx, rootDir, path)
if err != nil {
return err
}
var buf bytes.Buffer
for _, entry := range entries {
if entry.IsDir() {
buf.WriteString(fmt.Sprintf("[DIR] %s\n", entry.Name()))
} else {
buf.WriteString(fmt.Sprintf("[FILE] %s\n", entry.Name()))
}
}
// Save results
err = hostServiceClient.WriteFile(ctx, rootDir, "file_list.txt", buf.Bytes(), 0644)// Create temp file
tmpHandle, _ := hostServiceClient.FileCreateTemp(ctx, "/home/user", ".update-*")
// Write new content
writer, _ := hostServiceClient.FileWriter(ctx, tmpHandle)
writer.Write(newData)
writer.Close()
// Get temp file path (you'll need to track this)
// Close temp handle
hostServiceClient.FileClose(ctx, tmpHandle)
// Atomic rename
err := hostServiceClient.Rename(ctx, "/home/user", tmpPath, "final.txt")All operations return errors following Go conventions:
data, err := hostServiceClient.ReadFile(ctx, rootDir, path)
if err != nil {
// Check for specific error types
if os.IsNotExist(err) {
return fmt.Errorf("file not found: %w", err)
}
if os.IsPermission(err) {
return fmt.Errorf("permission denied: %w", err)
}
return fmt.Errorf("read error: %w", err)
}All operations accept a context for cancellation and timeouts:
// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
data, err := hostServiceClient.ReadFile(ctx, rootDir, path)
if err == context.DeadlineExceeded {
fmt.Println("Operation timed out")
}
// With cancellation
ctx, cancel := context.WithCancel(context.Background())
go func() {
// Cancel after some condition
cancel()
}()
stream, err := hostServiceClient.FileReader(ctx, handle, chunkSize)All operations respect security boundaries:
- Path confinement - rootDir + path pattern prevents escaping boundaries
- Client identification - Every request is tagged with client ID
- Audit trails - All operations logged with client, request ID, duration
- Capability-based - Foundation for restricting plugin access
Example logged request:
[INFO] host: ReadFile request from client:
client=019b05f0-a32c-7b3d-a0f1-5087d232174f
clientOwner=filelister
request=019b05f0-a343-7753-93aa-117a437c5321
root_dir=/home/user path=config.json
[INFO] host: ReadFile completed successfully:
client=019b05f0-a32c-7b3d-a0f1-5087d232174f
clientOwner=filelister
request=019b05f0-a343-7753-93aa-117a437c5321
duration_us=245 success=true
- Plugin Development - Use these APIs in your plugins
- Architecture - Understand how these operations are secured and logged
- TUI Guide - Try these operations interactively