API Reference
Use Envi programmatically in your Node.js applications.
Installation
pnpm add @codecompose/enviCommands
captureCommand()
Capture all environment files from a repository.
import { captureCommand } from "@codecompose/envi";
await captureCommand();restoreCommand()
Restore environment files from storage.
import { restoreCommand } from "@codecompose/envi";
await restoreCommand();clearCommand()
Delete stored configuration for the current project.
import { clearCommand } from "@codecompose/envi";
await clearCommand();createKeyCommand()
Generate envi.config.maml with a fresh encryption_key in the current repo.
import { createKeyCommand } from "@codecompose/envi";
await createKeyCommand({ force: false });globalClearCommand()
Delete entire envi directory and all stored configurations.
import { globalClearCommand } from "@codecompose/envi";
await globalClearCommand();GitHub Commands
import {
globalGithubEnableCommand,
globalGithubDisableCommand,
globalGithubRestoreCommand,
} from "@codecompose/envi";
// Enable GitHub integration
await globalGithubEnableCommand();
// Disable GitHub integration
await globalGithubDisableCommand();
// Restore from GitHub
await globalGithubRestoreCommand();Utilities
findRepoRoot()
Find the repository root by traversing up from current directory.
import { findRepoRoot } from "@codecompose/envi";
const repoRoot = await findRepoRoot();
// Returns: "/path/to/repo" or null if user cancelledfindEnvFiles()
Find all .env files in a directory.
import { findEnvFiles } from "@codecompose/envi";
const files = await findEnvFiles("/path/to/repo");
// Returns: [".env", "apps/web/.env.local", ...]parseEnvFile()
Parse a .env file with comment preservation.
import { parseEnvFile } from "@codecompose/envi";
const env = parseEnvFile("/path/to/.env");
// Returns: {
// __l_00: "# Comment",
// __i_00: "# Inline comment",
// KEY: "value"
// }Storage
getStorageDir()
Get the storage directory path.
import { getStorageDir } from "@codecompose/envi";
const dir = getStorageDir();
// Returns: "/Users/you/.envi/store"getEnviDir()
Get the envi root directory path.
import { getEnviDir } from "@codecompose/envi";
const dir = getEnviDir();
// Returns: "/Users/you/.envi"getPackageName()
Read package name from repository.
import { getPackageName } from "@codecompose/envi";
const name = getPackageName("/path/to/repo");
// Returns: "@org/package" or nullsaveToStorage()
Save environment configuration to storage.
import { saveToStorage } from "@codecompose/envi";
const hasChanges = saveToStorage(
"/path/to/repo",
[
{
path: ".env",
env: { KEY: "value" },
},
],
"@org/package", // Optional package name
{ encryptionKey: null }, // Optional encryption settings
);
// Returns: true if file was updated, false if no changesThe optional fourth argument is SaveToStorageOptions. When encryptionKey is set, each file's env block is encrypted with encrypt(JSON.stringify(env), encryptionKey) before being written, and the no-op comparison reads/decrypts the existing store with the same key.
Per-repo Config (envi.config.maml)
Helpers for the per-repo envi.config.maml file. The legacy filename envi.maml is still read so already-committed files keep working — reads prefer the canonical name and fall back to legacy; writes target whichever file is on disk (and create the canonical name when neither exists).
KEY_FILE_NAME
import { KEY_FILE_NAME } from "@codecompose/envi";
console.log(KEY_FILE_NAME); // "envi.config.maml"LEGACY_KEY_FILE_NAME
import { LEGACY_KEY_FILE_NAME } from "@codecompose/envi";
console.log(LEGACY_KEY_FILE_NAME); // "envi.maml"generateKey()
Generate a new 32-byte random key as a base64url string.
import { generateKey } from "@codecompose/envi";
const key = generateKey();
// Returns: ~43-character base64url stringreadEncryptionKey()
Read the encryption_key from envi.config.maml (preferred) or the legacy envi.maml (fallback). Emits a one-time info hint when the legacy file is read.
import { readEncryptionKey } from "@codecompose/envi";
const key = readEncryptionKey("/path/to/repo");
// Returns: string or nullreadCapturePatterns()
Read additional capture patterns from the per-repo config. Reads from whichever filename is on disk (canonical preferred). Returns [] when the file or capture_patterns field is missing.
import { readCapturePatterns } from "@codecompose/envi";
const patterns = readCapturePatterns("/path/to/repo");
// Returns: string[]writeEncryptionKey()
Write or update the per-repo config with a key. If only the legacy envi.maml is on disk, it is edited in place — migration to the canonical name is left to the user so they can commit the rename. Creates a new envi.config.maml with a header comment when neither file exists. Refuses to overwrite an existing encryption_key unless { force: true } is passed. Returns the basename of the file that was written (envi.config.maml or envi.maml).
import { writeEncryptionKey } from "@codecompose/envi";
const filename = writeEncryptionKey("/path/to/repo", key, { force: false });
// Throws Error when force is false and a key is already set.hasKeyFile()
Check whether either envi.config.maml or the legacy envi.maml exists in a repository (does not check whether encryption_key is set inside).
import { hasKeyFile } from "@codecompose/envi";
const exists = hasKeyFile("/path/to/repo");
// Returns: booleanfindKeyFile()
Return the basename of the per-repo config file present on disk — envi.config.maml if the canonical file exists, envi.maml if only the legacy file does, or null when neither exists. Use this to surface the actual filename in user-facing messages instead of always naming the canonical constant.
import { findKeyFile } from "@codecompose/envi";
const filename = findKeyFile("/path/to/repo");
// Returns: "envi.config.maml" | "envi.maml" | nullConfiguration
readConfig()
Read global configuration.
import { readConfig } from "@codecompose/envi";
const config = readConfig();
// Returns: {
// use_version_control: "github" | false,
// manifest_files: string[]
// }writeConfig()
Write global configuration.
import { writeConfig } from "@codecompose/envi";
writeConfig({
use_version_control: "github",
manifest_files: ["my-custom.json"],
});updateConfig()
Update global configuration (merges with existing).
import { updateConfig } from "@codecompose/envi";
// Update version control setting
updateConfig({ use_version_control: false });
// Add additional manifest files to check (on top of defaults)
updateConfig({
manifest_files: [
"my-custom.json", // Custom manifest file
"app.yaml", // Another custom manifest
],
});Multi-Language Support
getPackageName()
Extract package name from manifest files (supports multiple languages).
import { getPackageName } from "@codecompose/envi";
const name = getPackageName("/path/to/repo");
// Returns package name from:
// - package.json (JavaScript/TypeScript)
// - Cargo.toml (Rust)
// - go.mod (Go)
// - pyproject.toml (Python)
// - composer.json (PHP)
// - pubspec.yaml (Dart/Flutter)
// - settings.gradle.kts (Kotlin)
// - settings.gradle (Java/Gradle)
// - pom.xml (Java/Maven)DEFAULT_MANIFEST_FILES
List of default manifest files checked for package name detection.
import { DEFAULT_MANIFEST_FILES } from "@codecompose/envi";
console.log(DEFAULT_MANIFEST_FILES);
// [
// "package.json",
// "Cargo.toml",
// "go.mod",
// "pyproject.toml",
// "composer.json",
// "pubspec.yaml",
// "settings.gradle.kts",
// "settings.gradle",
// "pom.xml"
// ]PACKAGE_EXTRACTORS
Registry of all available package extractors.
import { PACKAGE_EXTRACTORS } from "@codecompose/envi";
// Find specific extractor
const rustExtractor = PACKAGE_EXTRACTORS.find(
(e) => e.filename === "Cargo.toml",
);
if (rustExtractor) {
const packageName = rustExtractor.extract("/path/to/rust/project");
console.log(packageName); // "my-rust-app"
}
// Try all extractors
for (const extractor of PACKAGE_EXTRACTORS) {
const name = extractor.extract("/path/to/project");
if (name) {
console.log(`Found ${name} from ${extractor.filename}`);
break;
}
}PackageExtractor Type
Interface for custom package extractors.
import type { PackageExtractor } from "@codecompose/envi";
const customExtractor: PackageExtractor = {
filename: "my-manifest.json",
extract: (repoPath: string): string | null => {
// Custom extraction logic
return "custom-package-name";
},
};Git Operations
isGitRepo()
Check if directory is a git repository.
import { isGitRepo } from "@codecompose/envi";
const isRepo = isGitRepo("/path/to/dir");
// Returns: booleaninitGitRepo()
Initialize a git repository.
import { initGitRepo } from "@codecompose/envi";
await initGitRepo("/path/to/dir");commitAndPush()
Commit all changes and push to remote.
import { commitAndPush } from "@codecompose/envi";
await commitAndPush("/path/to/repo", "Update env files");GitHub CLI
isGhInstalled()
Check if GitHub CLI is installed.
import { isGhInstalled } from "@codecompose/envi";
const installed = await isGhInstalled();
// Returns: booleanisGhAuthenticated()
Check if authenticated with GitHub CLI.
import { isGhAuthenticated } from "@codecompose/envi";
const authed = await isGhAuthenticated();
// Returns: booleangetGhUsername()
Get authenticated GitHub username.
import { getGhUsername } from "@codecompose/envi";
const username = await getGhUsername();
// Returns: "your-username"repoExists()
Check if a GitHub repository exists.
import { repoExists } from "@codecompose/envi";
const exists = await repoExists("envi-store");
// Returns: booleanTypes
EnviStore
Structure of stored MAML files.
import type { EnviStore, EnviStoreFile } from "@codecompose/envi";
const store: EnviStore = {
__envi_version: 1,
metadata: {
updated_from: "/path/to/repo",
updated_at: "2025-11-01T12:00:00.000Z",
},
files: [
{
path: ".env",
env: { KEY: "value" },
},
],
};EnviStore.files is an array of EnviStoreFile, a discriminated union:
type EnviStoreFile =
| { path: string; env: EnvObject } // plaintext
| { path: string; encrypted_env: string }; // ciphertext (base64)The encrypted_env variant is produced when saveToStorage is called with { encryptionKey }. The base64 string is encrypt(JSON.stringify(env), key) from ~/utils/encryption.
EnvObject
Parsed environment file object.
import type { EnvObject } from "@codecompose/envi";
const env: EnvObject = {
__l_00: "# Comment",
__i_00: "# Inline comment",
KEY: "value",
};EnviConfig
Global configuration structure.
import type { EnviConfig } from "@codecompose/envi";
const config: EnviConfig = {
use_version_control: "github" | false,
manifest_files: string[],
};
// Example with custom values (adds to defaults)
const customConfig: EnviConfig = {
use_version_control: false,
manifest_files: [
"my-custom.json", // Checked before defaults
"app.yaml",
],
};PackageExtractor
Package extractor interface.
import type { PackageExtractor } from "@codecompose/envi";
interface PackageExtractor {
/** The filename to look for in repository root */
filename: string;
/** Extract package name from file, returns null if extraction fails */
extract: (repositoryPath: string) => string | null;
}Example: Custom Backup Script
import {
findRepoRoot,
findEnvFiles,
parseEnvFile,
saveToStorage,
} from "@codecompose/envi";
import { join } from "node:path";
async function backupEnvFiles(repoPath: string) {
// Find repository root
const root = await findRepoRoot(repoPath);
if (!root) {
throw new Error("Not a repository");
}
// Find all env files
const filePaths = await findEnvFiles(root);
// Parse each file
const envFiles = filePaths.map((relativePath) => {
const absolutePath = join(root, relativePath);
const env = parseEnvFile(absolutePath);
return { path: relativePath, env };
});
// Save to storage
const hasChanges = saveToStorage(root, envFiles);
return { hasChanges, fileCount: envFiles.length };
}
// Usage
const result = await backupEnvFiles(process.cwd());
console.log(`Backed up ${result.fileCount} files`);