Skip to content

API Reference ​

Use Envi programmatically in your Node.js applications.

Installation ​

bash
pnpm add @codecompose/envi

Commands ​

captureCommand() ​

Capture all environment files from a repository.

typescript
import { captureCommand } from "@codecompose/envi";

await captureCommand();

restoreCommand(options?) ​

Restore environment files from storage.

typescript
import { restoreCommand } from "@codecompose/envi";

await restoreCommand();

/** Skip pulling a GitHub-backed store */
await restoreCommand({ pull: false });

clearCommand() ​

Delete stored configuration for the current project.

typescript
import { clearCommand } from "@codecompose/envi";

await clearCommand();

createKeyCommand() ​

Generate envi.config.maml with a fresh encryption_key in the current repo.

typescript
import { createKeyCommand } from "@codecompose/envi";

await createKeyCommand({ force: false });

globalClearCommand() ​

Delete entire envi directory and all stored configurations.

typescript
import { globalClearCommand } from "@codecompose/envi";

await globalClearCommand();

GitHub Commands ​

typescript
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.

typescript
import { findRepoRoot } from "@codecompose/envi";

const repoRoot = await findRepoRoot();
// Returns: "/path/to/repo" or null if user cancelled

findEnvFiles() ​

Find all .env files in a directory.

typescript
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.

typescript
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.

typescript
import { getStorageDir } from "@codecompose/envi";

const dir = getStorageDir();
// Returns: "/Users/you/.envi/store"

getEnviDir() ​

Get the envi root directory path.

typescript
import { getEnviDir } from "@codecompose/envi";

const dir = getEnviDir();
// Returns: "/Users/you/.envi"

getPackageName() ​

Read package name from repository.

typescript
import { getPackageName } from "@codecompose/envi";

const name = getPackageName("/path/to/repo");
// Returns: "@org/package" or null

saveToStorage() ​

Save environment configuration to storage.

typescript
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 changes

The 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 ​

typescript
import { KEY_FILE_NAME } from "@codecompose/envi";

console.log(KEY_FILE_NAME); // "envi.config.maml"

LEGACY_KEY_FILE_NAME ​

typescript
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.

typescript
import { generateKey } from "@codecompose/envi";

const key = generateKey();
// Returns: ~43-character base64url string

readEncryptionKey() ​

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.

typescript
import { readEncryptionKey } from "@codecompose/envi";

const key = readEncryptionKey("/path/to/repo");
// Returns: string or null

readCapturePatterns() ​

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.

typescript
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).

typescript
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).

typescript
import { hasKeyFile } from "@codecompose/envi";

const exists = hasKeyFile("/path/to/repo");
// Returns: boolean

findKeyFile() ​

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.

typescript
import { findKeyFile } from "@codecompose/envi";

const filename = findKeyFile("/path/to/repo");
// Returns: "envi.config.maml" | "envi.maml" | null

Configuration ​

readConfig() ​

Read global configuration.

typescript
import { readConfig } from "@codecompose/envi";

const config = readConfig();
// Returns: {
//   use_version_control: "github" | false,
//   manifest_files: string[]
// }

writeConfig() ​

Write global configuration.

typescript
import { writeConfig } from "@codecompose/envi";

writeConfig({
  use_version_control: "github",
  manifest_files: ["my-custom.json"],
});

updateConfig() ​

Update global configuration (merges with existing).

typescript
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).

typescript
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.

typescript
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.

typescript
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.

typescript
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.

typescript
import { isGitRepo } from "@codecompose/envi";

const isRepo = isGitRepo("/path/to/dir");
// Returns: boolean

initGitRepo() ​

Initialize a git repository.

typescript
import { initGitRepo } from "@codecompose/envi";

await initGitRepo("/path/to/dir");

commitAndPush() ​

Commit all changes and push to remote.

typescript
import { commitAndPush } from "@codecompose/envi";

await commitAndPush("/path/to/repo", "Update env files");

GitHub CLI ​

isGhInstalled() ​

Check if GitHub CLI is installed.

typescript
import { isGhInstalled } from "@codecompose/envi";

const installed = await isGhInstalled();
// Returns: boolean

isGhAuthenticated() ​

Check if authenticated with GitHub CLI.

typescript
import { isGhAuthenticated } from "@codecompose/envi";

const authed = await isGhAuthenticated();
// Returns: boolean

getGhUsername() ​

Get authenticated GitHub username.

typescript
import { getGhUsername } from "@codecompose/envi";

const username = await getGhUsername();
// Returns: "your-username"

repoExists() ​

Check if a GitHub repository exists.

typescript
import { repoExists } from "@codecompose/envi";

const exists = await repoExists("envi-store");
// Returns: boolean

Types ​

EnviStore ​

Structure of stored MAML files.

typescript
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:

typescript
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.

typescript
import type { EnvObject } from "@codecompose/envi";

const env: EnvObject = {
  __l_00: "# Comment",
  __i_00: "# Inline comment",
  KEY: "value",
};

EnviConfig ​

Global configuration structure.

typescript
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.

typescript
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 ​

typescript
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`);

Released under the MIT License.