Core library for building Codify plugins. Codify is an infrastructure-as-code tool that manages system resources (applications, CLI tools, and settings) through declarative JSON configuration files.
This library provides the foundational abstractions and runtime for creating Codify plugins. Plugins extend Codify's capabilities by implementing resources that can be created, modified, and destroyed on a system. Examples of resources include:
- CLI Tools: Homebrew, Docker, Git
- Applications: Google Chrome, VS Code, Zoom
- Settings: Git configs, AWS profiles, system preferences
npm install @codifycli/plugin-coreRequirements:
- Node.js >= 22.0.0
- TypeScript 5.x (for development)
Here's a minimal example of creating a plugin with a single resource:
import{Resource,ResourceSettings,Plugin,runPlugin,getPty}from'@codifycli/plugin-core';import{StringIndexedObject}from'@codifycli/schemas';// Define the resource configuration typeinterfaceGitConfigextendsStringIndexedObject{userName?: string;userEmail?: string;}// Implement the Resource abstract classclassGitConfigResourceextendsResource<GitConfig>{getSettings(): ResourceSettings<GitConfig>{return{id: 'git-config',operatingSystems: ['darwin','linux'],schema: {type: 'object',properties: {userName: {type: 'string'},userEmail: {type: 'string'}}}};}asyncrefresh(parameters: Partial<GitConfig>){constpty=getPty();constnameResult=awaitpty.spawnSafe('git config --global user.name');constemailResult=awaitpty.spawnSafe('git config --global user.email');return{userName: nameResult.status==='success' ? nameResult.data.trim() : undefined,userEmail: emailResult.status==='success' ? emailResult.data.trim() : undefined};}asynccreate(plan){constpty=getPty();constconfig=plan.desiredConfig!;if(config.userName){awaitpty.spawn(`git config --global user.name "${config.userName}"`);}if(config.userEmail){awaitpty.spawn(`git config --global user.email "${config.userEmail}"`);}}asyncdestroy(plan){constpty=getPty();awaitpty.spawn('git config --global --unset user.name');awaitpty.spawn('git config --global --unset user.email');}}// Create and run the pluginconstplugin=Plugin.create('my-plugin',[newGitConfigResource()]);runPlugin(plugin);The top-level container that manages multiple resource types. Handles IPC communication with the Codify CLI.
constplugin=Plugin.create('plugin-name',[newResource1(),newResource2(),// ... more resources]);runPlugin(plugin);The fundamental building block representing a manageable system entity. Resources must implement:
getSettings(): Return resource configuration (id, schema, OS support, etc.)refresh(parameters, context): Query the current system statecreate(plan): Install/create the resourcedestroy(plan): Uninstall/remove the resourcemodify(parameterChange, plan): Update individual parameters (optional)
Represents a set of changes needed to transform the current state into the desired state. Plans contain:
- Resource Operation: CREATE, DESTROY, MODIFY, RECREATE, or NOOP
- Parameter Changes: Individual parameter-level operations (ADD, REMOVE, MODIFY, NOOP)
The planning workflow:
- Validate: Check user configuration against schema
- Plan: Compare desired vs. current state, generate change set
- Apply: Execute the plan to make changes
Stateless Mode (default):
- Plans computed by comparing desired config against current system state
- Only manages parameters explicitly declared in config
- No destroy operations (removing from config = ignored by Codify)
Stateful Mode:
- Tracks previous state between runs
- Supports destroy operations
- Plans compare desired vs. state, then match state to current system
- Enables granular change detection
Parameters with their own lifecycle, tied to the parent resource. Examples:
- Homebrew formulas (can be installed/uninstalled within Homebrew)
- NVM Node versions (managed within NVM)
import{StatefulParameter}from'@codifycli/plugin-core';classBrewFormulaParameterextendsStatefulParameter<BrewConfig,string[]>{asyncrefresh(desired,config){constpty=getPty();constresult=awaitpty.spawn('brew list --formula');returnresult.data.split('\n').filter(Boolean);}asyncadd(formulas,plan){constpty=getPty();awaitpty.spawn(`brew install --formula ${formulas.join(' ')}`);}asyncremove(formulas,plan){constpty=getPty();awaitpty.spawn(`brew uninstall --formula ${formulas.join(' ')}`);}asyncmodify(newValue,previousValue,plan){// Handle formula updates}}Register in resource settings:
getSettings()
:
ResourceSettings<BrewConfig>{return{id: 'homebrew',parameterSettings: {formulas: {type: 'stateful',implementation: newBrewFormulaParameter()}}};}Execute shell commands through the PTY abstraction:
import{getPty}from'@codifycli/plugin-core';constpty=getPty();// Spawn command (throws on non-zero exit)constresult=awaitpty.spawn('brew install jq');// Spawn safely (returns result with status)constsafeResult=awaitpty.spawnSafe('which jq');if(safeResult.status==='success'){console.log(safeResult.data);}// With optionsawaitpty.spawn('npm install',{cwd: '/path/to/project',env: {NODE_ENV: 'production'},interactive: true,requiresRoot: false});Two PTY implementations:
- BackgroundPty: Async execution during refresh/plan (killed after planning)
- SequentialPty: Sync execution during apply operations
Configure resource behavior via ResourceSettings<T>:
getSettings(): ResourceSettings<MyConfig>{return{// Required: unique type identifierid: 'my-resource',// Required: supported operating systemsoperatingSystems: ['darwin','linux','win32'],// Optional: supported Linux distributionslinuxDistros: ['ubuntu','debian','fedora'],// Schema for validation (JSON Schema or Zod)schema: {type: 'object',properties: {version: {type: 'string'},path: {type: 'string'}},required: ['version']},// Allow multiple instancesallowMultiple: {identifyingParameters: ['name','path'],matcher: (desired,current)=>desired.name===current.name},// Prevent resource from being destroyedcanDestroy: false,// Mark as sensitive (prevents auto-import)isSensitive: true,// Resource dependenciesdependencies: ['other-resource-id'],// Per-parameter settingsparameterSettings: {path: {inputTransformation: {to: (input)=>untildify(input),// Expand ~from: (current)=>tildify(current)// Convert to ~}},apiKey: {isSensitive: true// Hide in plan output},tags: {type: 'array',isElementEqual: (a,b)=>a.name===b.name}}};}static create(name: string, resources: Resource[]): Pluginasync initialize(data): Promise<InitializeResponseData>async plan(data): Promise<PlanResponseData>async apply(data): Promise<void>async validate(data): Promise<ValidateResponseData>async import(data): Promise<ImportResponseData>async match(data): Promise<MatchResponseData>
abstract getSettings(): ResourceSettings<T>async initialize(): Promise<void>async validate(parameters): Promise<void>abstract refresh(parameters, context): Promise<T | T[] | null>abstract create(plan: CreatePlan<T>): Promise<void>abstract destroy(plan: DestroyPlan<T>): Promise<void>async modify(change: ParameterChange<T>, plan: ModifyPlan<T>): Promise<void>
id: stringchangeSet: ChangeSet<T>coreParameters: ResourceConfigisStateful: booleandesiredConfig: T | nullcurrentConfig: T | nullrequiresChanges(): booleantoResponse(): PlanResponseData
getSettings(): ParameterSettingabstract refresh(desired, config): Promise<V | null>abstract add(value, plan): Promise<void>abstract modify(newValue, previousValue, plan): Promise<void>abstract remove(value, plan): Promise<void>
// PTY accessgetPty(): IPty// Path utilitiestildify(absolutePath: string): stringuntildify(pathWithTilde: string): stringresolvePathWithVariables(path: string): stringaddVariablesToPath(absolutePath: string): string// File utilitiesfileExists(path: string): Promise<boolean>directoryExists(path: string): Promise<boolean>// Array utilitiesareArraysEqual<T>(a: T[],b: T[],isEqual?: (a: T,b: T)=>boolean): booleanThe library includes a codify-build CLI tool for plugin development:
# Generate plugin documentation and validate schemas
npx codify-buildThis tool expects a plugin implementation with a src/resources/ directory structure.
# Install dependencies
npm install
# Run tests
npm test# Build
npx tsc
# Lint
npx eslint src/See the @codifycli/plugin-core tests for more examples:
src/plugin/plugin.test.ts- Plugin lifecyclesrc/resource/resource-controller.test.ts- Resource operationssrc/plan/plan.test.ts- Plan calculationsrc/stateful-parameter/stateful-parameter-controller.test.ts- Stateful parameters
ISC