Enkryptify CLI is a command-line tool that securely manages and injects secrets from various secret management providers (Enkryptify, AWS, etc.) directly into your commands and applications.
ek loginThis will authenticate you with your default provider (Enkryptify). For other providers:
ek login --provider enkryptify
ek login --provider awsNavigate to your project directory and run the configure command. You only need to do this once per project (works for both backend and frontend):
cd /path/to/your/project
ek configureThe configuration process varies by provider. For Enkryptify, the interactive setup will guide you through:
- Selecting your workspace
- Choosing your project
- Selecting the environment
Once configured, the CLI automatically detects your project settings when you run commands from this directory or any subdirectory.
Run any command with secrets automatically injected as environment variables. The -- separator is optional (only needed if you have flags that might conflict):
ek run npm start
ek run python app.py
ek run --env production npm run deploy
ek run -- pnpm run dev # -- is optionalSecrets are automatically fetched from your provider and injected as environment variables, making them available to your application at runtime.
src/
├── cmd/ # CLI command handlers
│ ├── index.ts # Command registry
│ ├── login.ts
│ ├── configure.ts
│ ├── run.ts
│ ├── listCommand.ts
│ ├── create.ts
│ ├── update.ts
│ └── delete.ts
├── providers/ # Provider implementations
│ ├── base/ # Interfaces
│ │ ├── Provider.ts
│ │ └── AuthProvider.ts
│ ├── registry/ # Provider registry
│ │ ├── ProviderRegistry.ts
│ │ └── index.ts
│ └── enkryptify/ # Enkryptify provider
│ ├── provider.ts
│ ├── auth.ts
│ └── httpClient.ts
├── lib/ # Shared utilities
│ ├── config.ts # All config management (auth + project)
|
└── ui/ # Ink UI components
Purpose: Contains all CLI command implementations. Commands are thin coordinators that delegate all actual work to providers.
What it does:
- Defines command structure (name, options, arguments)
- Validates user input
- Gets provider from registry
- Delegates to provider methods
- Handles errors consistently
Key principle: Commands don't contain provider-specific logic. They coordinate the flow but let providers do the work.
Purpose: Contains all secret management provider integrations. Each provider knows how to authenticate, fetch secrets, and perform CRUD operations on secrets.
What it does:
- Implements provider-specific logic (API calls, data transformation)
- Handles authentication via AuthProvider
- Manages secrets (list, create, update, delete, inject)
- Provides project configuration flows
Key principle: All providers implement the same Provider interface, so commands work with any provider without knowing which one.
Purpose: Contains reusable utilities used across the entire CLI.
What it does:
- Manages configuration (project configs, provider settings)
- Handles secret injection into environment variables
- Provides secure credential storage via system keyring
- Offers consistent error logging and user input helpers
Key principle: Single source of truth for common operations. Used by commands, providers, and UI components.
Purpose: Contains React components that render in the terminal using Ink.
What it does:
- Provides interactive UI for commands
- Shows loading states and progress
- Displays data in tables and formatted views
- Handles user selections and confirmations
Central registry where all commands are registered.
export function registerCommands(program: Command) {
registerLoginCommand(program);
registerConfigureCommand(program);
registerRunCommand(program);
registerListCommand(program);
registerCreateCommand(program);
registerUpdateCommand(program);
registerDeleteCommand(program);
}How it works:
src/cli.tscalls this on startup- All commands become available to users
- To add a command: create file in
cmd/, import and register here
Complete example showing how commands work:
export function registerLoginCommand(program: Command) {
program
.command("login")
.option("-p, --provider <name>", "Provider name")
.option("-f, --force", "Force re-authentication")
.action(async (options) => {
// 1. Determine provider
const providerName = options.provider || "enkryptify";
// 2. Get provider from registry
const provider = providerRegistry.get(providerName);
if (!provider) {
logError(`Provider "${providerName}" not found`);
process.exit(1);
}
// 3. Show UI flow (which calls provider.login internally)
await LoginFlow({ provider, options });
});
}Pattern: Validate → Get Provider → Delegate → Handle Errors
All commands follow the same pattern:
cmd/configure.ts- Gets provider → callsprovider.configure()→ saves configcmd/run.ts- Finds config → gets provider → callsprovider.run()→ injects secrets → executes commandcmd/list.ts- Finds config → gets provider → callsprovider.listSecrets()→ displays tablecmd/create.ts- Finds config → gets provider → callsprovider.createSecret()cmd/update.ts- Finds config → gets provider → callsprovider.updateSecret()cmd/delete.ts- Finds config → gets provider → callsprovider.deleteSecret()
This is the main interface that ALL providers must implement.
The Provider interface defines methods for:
- Authentication -
login()- delegates to AuthProvider - Project Setup -
configure()- guides user through workspace/project/environment selection - Secret CRUD Operations:
listSecrets()- List all secrets (with optional value display)createSecret()- Create a new secretupdateSecret()- Update an existing secretdeleteSecret()- Delete a secret
- Secret Injection -
run()- Fetch secrets and return them for injection
export interface Provider {
readonly name: string;
// Authentication (delegates to AuthProvider)
login(options?: LoginOptions): Promise<void>;
// Project configuration
configure(options: string): Promise<ProjectConfig>;
// Secret CRUD operations
listSecrets(config: ProjectConfig, showValues?: string): Promise<Secret[]>;
createSecret(config: ProjectConfig, name: string, value: string): Promise<void>;
updateSecret(config: ProjectConfig, name: string, isPersonal?: boolean): Promise<void>;
deleteSecret(config: ProjectConfig, name: string): Promise<void>;
// Secret injection (fetch secrets for running commands)
run(config: ProviderConfig, options?: runOptions): Promise<Secret[]>;
}Key Points:
- Provider handles ALL secret management operations
- Provider.login() is just a delegation method - it calls AuthProvider.login()
- Commands use this interface, so they work with any provider
AuthProvider is ONLY responsible for authentication and credential storage.
It does NOT handle secrets. It only:
- Login -
login()- Handles OAuth flow, token exchange, etc. - Get Credentials -
getCredentials()- Retrieves stored tokens/credentials
export interface AuthProvider {
// Handle authentication flow (OAuth, PKCE, etc.)
login(options?: LoginOptions): Promise<void>;
// Retrieve stored credentials (tokens, API keys, etc.)
getCredentials(): Promise<Credentials>;
}Key Points:
- AuthProvider ONLY handles login and credential storage
- AuthProvider does NOT know about secrets
- Provider makes HTTP requests - HTTP client interceptor automatically gets token from keyring and adds to request headers
auth.getCredentials()exists but is rarely needed - HTTP client handles it automatically- Separation of concerns: Auth = AuthProvider, Secrets = Provider
export class EnkryptifyProvider implements Provider {
private auth: EnkryptifyAuth; // AuthProvider instance
constructor() {
this.auth = new EnkryptifyAuth();
}
// Provider.login() just delegates to AuthProvider
async login(options?: LoginOptions): Promise<void> {
await this.auth.login(options);
// AuthProvider handles:
// - OAuth PKCE flow
// - Browser opening
// - Token exchange
// - Storing credentials in keyring
}
// Provider methods make HTTP requests
// HTTP client interceptor automatically gets token from keyring and adds to headers
async run(config: ProjectConfig, options?: runOptions): Promise<Secret[]> {
// HTTP client automatically injects token via interceptor
// No need to manually call auth.getCredentials()
const response = await http.get("/v1/secrets");
// Transform and return secrets
return transformSecrets(response.data);
}
// Same pattern for other methods
// HTTP client handles authentication automatically
async listSecrets(config: ProjectConfig): Promise<Secret[]> {
const response = await http.get("/v1/secrets");
// Transform and return...
}
async createSecret(config: ProjectConfig, name: string, value: string): Promise<void> {
await http.post("/v1/secrets", { name, value });
// HTTP client automatically adds auth token
}
}Flow:
- User runs
ek login - Command calls
LoginFlow({ provider })- shows UI - UI component calls
provider.login() - Provider delegates to
authProvider.login()- handles OAuth, stores credentials in keyring - User runs
ek run npm start - Command calls
provider.run() - Provider makes HTTP request - HTTP client interceptor automatically gets token from keyring and adds to headers
- Provider returns secrets
- Command injects secrets and runs user's command
Central registry that stores and retrieves providers.
export class ProviderRegistry {
private providers: Map<string, Provider> = new Map();
register(provider: Provider): void {
this.providers.set(provider.name, provider);
}
get(name: string): Provider | undefined {
return this.providers.get(name);
}
list(): Provider[] {
return Array.from(this.providers.values());
}
has(name: string): boolean {
return this.providers.has(name);
}
}
export const providerRegistry = new ProviderRegistry();How it works:
- Singleton instance shared across application
- Providers register on startup in
providers/registry/index.ts - Commands look up providers by name
providers/registry/index.ts - Registration Point
import { EnkryptifyProvider } from "@/providers/enkryptify/provider";
import { providerRegistry } from "@/providers/registry/ProviderRegistry";
// Register all providers here
providerRegistry.register(new EnkryptifyProvider());
// Future: providerRegistry.register(new AwsProvider());
export { providerRegistry };To add a new provider:
- Create provider class implementing
Providerinterface - Create AuthProvider class implementing
AuthProviderinterface - Create login UI component in
ui/folder (e.g.,ui/YourProviderLogin.tsx) - Register UI component in
ui/LoginFlow.tsx(add case in switch statement) - Import and register provider in
providers/registry/index.ts - Done! Commands automatically work with new provider
Important: You MUST create and register a login UI component. Without it, the login command will show "Unknown provider" error.
Manages all configuration (project configs and provider settings) in ~/.enkryptify/config.json.
Available Methods:
export const config = {
// Project configuration
findProjectConfig(startPath: string): Promise<ProjectConfig>
// Walks up directory tree to find project config
getConfigure(projectPath: string): Promise<ProjectConfig | null>
// Gets exact project config
createConfigure(projectPath: string, projectConfig: ProjectConfig): Promise<void>
// Saves project configuration
// Provider settings
getProvider(providerName: string): Promise<Record<string, string> | null>
// Gets provider settings
updateProvider(providerName: string, settings: Record<string, string>): Promise<void>
// Updates provider settings
};Safely injects secrets as environment variables.
export function buildEnvWithSecrets(secrets: Secret[]): typeof process.env {
const env = { ...process.env };
for (const secret of secrets) {
// Protect dangerous env vars (PATH, LD_PRELOAD, etc.)
if (isDangerousEnvVar(secret.name)) {
console.warn(`Warning: ${secret.name} is protected`);
continue;
}
env[secret.name] = secret.value;
}
return env;
}Features:
- Merges secrets into environment variables
- Protects dangerous vars from override
- Validates secret names
Provides secure storage using OS keyring.
export const keyring = {
set(key: string, value: string): Promise<void>,
get(key: string): Promise<string | null>,
delete(key: string): Promise<void>,
has(key: string): Promise<boolean>,
};Security:
- Uses OS keyring (macOS Keychain, Windows Credential Manager, Linux Secret Service)
- Credentials never in plain text files
- Service name:
"enkryptify-cli"
lib/error.ts- Consistent error logging (logError())lib/input.ts- User input helpers (getTextInput(),getSecureInput())lib/terminal.ts- Terminal utilities (setupTerminalCleanup())
Ink is React for the terminal. Uses React patterns but renders to terminal:
<Box>= layout container<Text>= text display- No CSS - use props like
padding,color, etc.
Orchestrates login UI and routes to provider-specific components.
import { EnkryptifyLogin } from "./EnkryptifyLogin";
import { AwsLogin } from "./AwsLogin";
import { YourProviderLogin } from "./YourProviderLogin"; // Import your component
function LoginFlowComponent({ provider, options, onError, onComplete }) {
const renderProviderComponent = () => {
switch (provider.name) {
case "enkryptify":
return <EnkryptifyLogin provider={provider} options={options} onError={onError} onComplete={onComplete} />;
case "aws":
return <AwsLogin provider={provider} options={options} onError={onError} onComplete={onComplete} />;
case "yourprovider": // Add your provider case
return <YourProviderLogin provider={provider} options={options} onError={onError} onComplete={onComplete} />;
default:
return <Text>Unknown provider: {provider.name}</Text>;
}
};
return <Box>{renderProviderComponent()}</Box>;
}Important: When adding a new provider, you MUST:
- Create a login UI component (e.g.,
ui/YourProviderLogin.tsx) - Import it in
ui/LoginFlow.tsx - Add a case in the switch statement for your provider name
Shows loading state and calls provider.login().
export function EnkryptifyLogin({ provider, onComplete, onError }) {
const [status, setStatus] = useState("loading");
useEffect(() => {
const performLogin = async () => {
try {
setMessage("Authenticating...");
await provider.login(options); // Calls Provider.login()
setStatus("success");
onComplete();
} catch (error) {
setStatus("error");
onError(error);
}
};
void performLogin();
}, []);
return (
<Box>
{status === "loading" && <Spinner />}
{status === "success" && <Text>✓ Success</Text>}
{status === "error" && <Text color="red">Error</Text>}
</Box>
);
}Example: User runs ek login --provider enkryptify
1. cli.ts
- Parses command: "login"
- Routes to registerLoginCommand
2. cmd/login.ts
- Gets provider from registry: providerRegistry.get("enkryptify")
- Calls LoginFlow({ provider })
3. ui/LoginFlow.tsx
- Renders EnkryptifyLogin component
4. ui/EnkryptifyLogin.tsx
- Shows "Authenticating..." spinner
- Calls provider.login(options)
5. providers/enkryptify/provider.ts
- Delegates to auth.login(options)
6. providers/enkryptify/auth.ts
- Runs OAuth PKCE flow
- Opens browser
- User authenticates
- Exchanges code for token
- Stores in keyring via lib/keyring.ts
7. Flow completes
- UI shows "✓ Success"
- Command completes
The CLI stores all configuration in ~/.enkryptify/config.json.
{
"setups": {
"/absolute/path/to/project": {
"provider": "enkryptify",
"workspace_slug": "workspace-name",
"workspace_name": "Workspace Name",
"project_slug": "project-name",
"project_name": "Project Name",
"environment_id": "uuid-here"
}
},
"providers": {
"enkryptify": {}
}
}setups: Maps project directory paths to their configurationproviders: Provider-specific settings
The CLI automatically finds the configuration for your current project by walking up the directory tree.
Before using the AWS provider:
-
Create AWS Access Key:
-
Install AWS CLI:
- Install the AWS CLI (https://aws.amazon.com/cli/)
-
Configure AWS CLI: aws configure
- Enter your Access Key ID
- Enter your Secret Access Key
- Enter your default region (e.g.,
us-east-1)
-
Verify Setup:
ek login --provider aws This verifies your AWS setup is correct. If successful, proceed to configure your project.
-
Configure Your Project:
ek configure --provider aws
Before using the GCP provider:
-
Install Google Cloud SDK:
- Install the Google Cloud SDK (https://cloud.google.com/sdk/docs/install)
-
Initialize Your Project:
gcloud init
- Login and select the project you want to use for fetch and CRUD operations
-
Authenticate:
gcloud auth application-default login
-
Verify Setup: ek login --provider gcp This verifies your GCP setup is correct. If successful, proceed to configure your project.
-
Configure Your Project: ek configure --provider gcp
No setup required. Simply run:
ek login ek configure
Note: For Enkryptify, you don't need to specify --provider for the login and configure commands . Only use --provider when using other providers like AWS or GCP.
Authenticate with a secret management provider.
Options:
--provider, -p <name>- Provider name (defaults to 'enkryptify')--force, -f- Force re-authentication even if already logged in
Examples:
ek login
ek login --provider enkryptify
ek login --force # Re-authenticateConfigure your project to use a specific provider and environment.
Examples:
ek configure
ek configure enkryptifyProvider-specific behavior for --env:
-
Enkryptify: Environment name (e.g.,
production,staging) -
Google Cloud Platform: Project ID
-
AWS: Secrets Manager prefix
Run a command with secrets injected as environment variables.
Options:
--env, -e <name>- Environment name to use (overrides default from config) for this run only.
Note: The -- separator is optional. You can run commands directly without it. Only use -- if you have flags that might conflict with the CLI's options.
Examples:
ek run npm start
ek run python app.py
ek run --env production -- npm run deploy
ek run -- pnpm run dev # -- is optional
ek run --env production pnpm dev run
List all secrets in the current environment.
Options:
--show, -s- Show secret values (default: masked)
Examples:
ek list
ek list --show # Show actual valuesCreate a new secret in the current environment.
Arguments:
<name>- Secret name (required) - can only contain A-Z, a-z, 0-9, underscore (_), and hyphen (-)[value]- Secret value (optional, will prompt if not provided) use "" for complex values
Examples:
ek create API_KEY # Will prompt for valueNote: The --ispersonal option is only available for the Enkryptify provider.
Update an existing secret.
Arguments:
<name>- Secret name to update
Options:
-
--ispersonal- Mark as personal secret (overrides team secret)Note: The
--ispersonaloption is only available for the Enkryptify provider.
Examples:
ek update DATABASE_URL
ek update API_KEY --ispersonal
Delete a secret.
Arguments:
<name>- Secret name to delete
Examples:
ek delete OLD_SECRET