API Reference
Overview
Nakimi provides both a CLI and a Python API for programmatic access. This document covers the Python API for plugin development and integration.
Core Modules
nakimi.core
The core module provides vault management and configuration.
from nakimi.core import Vault, get_config, secure_delete
Vault Class
Main vault interface for encryption/decryption operations.
class Vault:
def __init__(self, config=None):
"""Initialize vault with optional config."""
def decrypt(self, encrypted_path: str) -> str:
"""Decrypt file and return path to temporary plaintext.
Args:
encrypted_path: Path to .age encrypted file
Returns:
Path to temporary plaintext file (in RAM disk)
Raises:
DecryptionError: If decryption fails
"""
def encrypt(self, plaintext_path: str, output_path: str = None) -> str:
"""Encrypt file with vault's public key.
Args:
plaintext_path: Path to plaintext file
output_path: Optional output path (defaults to plaintext_path + '.age')
Returns:
Path to encrypted file
"""
def init(self, force: bool = False) -> tuple[str, str]:
"""Generate new age key pair.
Args:
force: Overwrite existing keys
Returns:
Tuple of (private_key_path, public_key_path)
"""
get_config() Function
def get_config() -> Config:
"""Get current configuration.
Reads from (in order):
1. Environment variables
2. Config file (~/.config/nakimi/config)
3. Default values
Returns:
Config object with attributes:
- vault_dir: Path to vault directory
- key_file: Path to private key
- secrets_file: Path to encrypted secrets
"""
secure_delete() Function
def secure_delete(path: str) -> None:
"""Securely delete a file using shred.
Args:
path: File to delete
Note:
Falls back to regular deletion if shred unavailable
"""
nakimi.core.plugin
Plugin base classes and utilities.
from nakimi.core.plugin import Plugin, PluginCommand, PluginError
Plugin Abstract Base Class
Base class for all plugins.
class Plugin(ABC):
@property
@abstractmethod
def name(self) -> str:
"""Plugin name (must match secrets.json key)."""
@property
@abstractmethod
def description(self) -> str:
"""Short description shown in plugin list."""
@abstractmethod
def get_commands(self) -> list[PluginCommand]:
"""Return list of commands this plugin provides."""
def _validate_secrets(self) -> None:
"""Validate plugin-specific secrets.
Override to check required fields in self.secrets.
Raises PluginError if validation fails.
"""
# Available attributes:
# self.secrets: Dict[str, Any] - Plugin's secrets from vault
# self.config: Config - Global configuration
PluginCommand Class
@dataclass
class PluginCommand:
name: str
description: str
handler: Callable
args: list[str] # Argument names for help display
PluginError Exception
class PluginError(Exception):
"""Base exception for plugin errors."""
Plugin Development Example
Complete Plugin Implementation
# src/nakimi/plugins/example/plugin.py
from nakimi.core.plugin import Plugin, PluginCommand, PluginError
class ExamplePlugin(Plugin):
@property
def name(self) -> str:
return "example"
@property
def description(self) -> str:
return "Example plugin demonstrating the API"
def _validate_secrets(self):
# Check required fields
required = ['api_key', 'endpoint']
missing = [f for f in required if f not in self.secrets]
if missing:
raise PluginError(f"Missing required fields: {missing}")
def get_commands(self) -> list[PluginCommand]:
return [
PluginCommand(
name="status",
description="Check service status",
handler=self.cmd_status,
args=[]
),
PluginCommand(
name="fetch",
description="Fetch data by ID",
handler=self.cmd_fetch,
args=["id"]
),
]
def cmd_status(self) -> str:
"""Command handler example."""
import requests
try:
response = requests.get(
self.secrets['endpoint'] + '/status',
headers={'Authorization': f"Bearer {self.secrets['api_key']}"}
)
response.raise_for_status()
return f"Service status: {response.json()['status']}"
except Exception as e:
return f"Error: {e}"
def cmd_fetch(self, item_id: str) -> str:
"""Command handler with arguments."""
# Arguments come as strings from CLI
return f"Would fetch item {item_id}"
Plugin Registration
Plugins are auto-discovered via Python’s entry point system. No manual registration needed.
Requirements:
- Plugin class must be in
plugin.pyfile - Located in
src/nakimi/plugins/<plugin_name>/ - Class name must be
<PluginName>Plugin(e.g.,ExamplePlugin)
Using Plugins Programmatically
Direct Plugin Instantiation
from nakimi.core import Vault, get_config
from nakimi.plugins.gmail.plugin import GmailPlugin
# Load secrets
config = get_config()
vault = Vault()
temp_path = vault.decrypt(config.secrets_file)
import json
with open(temp_path) as f:
secrets = json.load(f)
# Instantiate plugin
plugin = GmailPlugin()
plugin.secrets = secrets['gmail']
plugin.config = config
# Use plugin commands
commands = plugin.get_commands()
status_cmd = next(c for c in commands if c.name == 'profile')
result = status_cmd.handler() # Calls cmd_profile()
print(result)
# Clean up
secure_delete(temp_path)
Plugin Manager
For advanced use cases, use the plugin manager directly:
from nakimi.core.plugin_manager import PluginManager
manager = PluginManager()
manager.load_plugins(secrets) # secrets dict from vault
# Get specific plugin
gmail_plugin = manager.get_plugin('gmail')
# Execute command
result = manager.execute_command('gmail', 'unread', ['5'])
print(result)
CLI Integration
Adding New CLI Commands
Plugins automatically get CLI commands via the pattern:
nakimi <plugin_name>.<command_name> [args]
Example: nakimi example.fetch 123
Custom Argument Parsing
For complex arguments, use argparse style in your command handler:
def cmd_search(self, query: str, limit: str = "10") -> str:
# limit comes as string from CLI
limit_int = int(limit)
# ... implementation
Session Management
Programmatic Sessions
from nakimi.core.session import Session
with Session() as session:
# session.decrypted_path available
secrets = session.load_secrets()
# Use secrets...
gmail_secrets = secrets['gmail']
# Session automatically cleans up on exit
Error Handling
Common Exceptions
from nakimi.core.exceptions import (
DecryptionError, # Age decryption failed
EncryptionError, # Age encryption failed
ConfigError, # Configuration problem
PluginLoadError, # Plugin failed to load
CommandError, # Command execution failed
)
try:
vault.decrypt('secrets.json.age')
except DecryptionError as e:
print(f"Decryption failed: {e}")
Plugin Error Reporting
Plugins should raise PluginError for user-facing errors:
def cmd_send_email(self, to: str, subject: str, body: str) -> str:
if not to or '@' not in to:
raise PluginError("Invalid email address")
# ... implementation
Configuration API
Environment Variables
import os
os.environ['NAKIMI_DIR'] = '/custom/path'
os.environ['NAKIMI_KEY'] = '/custom/key.txt'
# Reload config
from nakimi.core import reload_config
config = reload_config()
Config File
from nakimi.core.config import write_config
write_config({
'vault_dir': '~/.nakimi',
'key_file': '~/.nakimi/key.txt',
'secrets_file': '~/.nakimi/secrets.json.age',
})
Testing Utilities
Plugin Testing
from nakimi.core.plugin import Plugin
from nakimi.core.config import Config
class TestPlugin(Plugin):
# ... implementation
def test_plugin():
plugin = TestPlugin()
plugin.secrets = {'api_key': 'test'}
plugin.config = Config()
# Test validation
plugin._validate_secrets() # Should not raise
# Test commands
commands = plugin.get_commands()
assert len(commands) > 0
# Test command execution
result = commands[0].handler()
assert isinstance(result, str)
Mock Vault for Tests
from unittest.mock import patch, Mock
from nakimi.core import Vault
def test_with_mock_vault():
mock_vault = Mock(spec=Vault)
mock_vault.decrypt.return_value = '/tmp/test.json'
with patch('nakimi.core.Vault', return_value=mock_vault):
# Test code that uses vault
pass
Performance Considerations
Lazy Loading
Plugins are lazy-loaded - they only initialize when first accessed.
Secret Caching
Secrets are cached per session. Repeated decryption within a session uses cached plaintext.
Memory Usage
Large secrets may impact memory usage. Consider:
- Splitting secrets into multiple plugin sections
- Using references to external encrypted files
- Cleaning up unused plugin instances
Best Practices
API Usage
- Always clean up: Use context managers or explicit
secure_delete() - Validate inputs: Check arguments before processing
- Handle errors gracefully: Provide helpful error messages
- Log appropriately: Use Python’s logging module for debugging
- Test thoroughly: Write unit tests for your plugin code
Security
- Never log secrets: Avoid printing secrets to console or logs
- Minimize exposure: Keep plaintext in memory for shortest time possible
- Validate TLS: Ensure API calls use proper certificate validation
- Sanitize inputs: Prevent injection attacks in command handlers
Examples
See the existing plugins for real-world examples:
src/nakimi/plugins/gmail/- Gmail API integrationsrc/nakimi/plugins/calendar/- Google Calendar integration (planned)
Further Reading
Last updated: 2026-02-01