Programmatic API
AgentsMesh ships as a typed ESM library alongside the CLI. Every CLI capability — generate, import, lint, diff, check — is exposed as a function you can call from a script, CI workflow, IDE extension, or another tool.
Available since v0.6. Requires Node.js 20+.
Entrypoints
| Subpath | Use when |
|---|---|
agentsmesh | Full surface in one import — runtime functions, error classes, canonical types, target-descriptor types. |
agentsmesh/engine | Generation, import, lint, diff, check + error taxonomy + loadProjectContext + config loaders + the types those functions need. |
agentsmesh/canonical | loadCanonical, loadCanonicalFiles, LoadCanonicalOptions, and the canonical domain types. |
agentsmesh/targets | Built-in catalog (getTargetCatalog, getAllDescriptors, getDescriptor), registerTargetDescriptor for plugins, and target-descriptor types. |
Every entrypoint resolves to a real .d.ts under strict TypeScript — there is no TS7016: Could not find a declaration file for any public symbol. The tests/consumer-smoke/ job in CI installs the packed tarball into a clean strict-mode TS project on every push and tsc --noEmits every export below.
Quick start — CLI-parity generation
import { loadProjectContext, generate } from 'agentsmesh';
const project = await loadProjectContext(process.cwd());const results = await generate(project);loadProjectContext() mirrors the CLI setup path: it loads scoped config, bootstraps plugin descriptors, merges extends, installed packs, and local .agentsmesh/ content, then returns an object directly usable by generate, lint, and diff. results is an array of { target, path, content, status } — exactly what the CLI would write, but no files have been written yet. Inspect, transform, or write them yourself.
Loading project context
loadProjectContext(projectRoot, options?)
Loads the same execution context the CLI uses and returns { config, canonical, projectRoot, scope, configDir, canonicalDir }.
import { loadProjectContext, type ProjectContext, type LoadProjectContextOptions,} from 'agentsmesh';
const options: LoadProjectContextOptions = { scope: 'project', // or 'global' refreshRemoteCache: false, // set true to refetch remote extends};
const ctx: ProjectContext = await loadProjectContext(process.cwd(), options);Use this helper for scripts that should match CLI output for real projects using plugins, packs, or extends.
Loading config
loadConfig(projectRoot)
Searches upward from projectRoot for agentsmesh.yaml, parses it, validates against the Zod schema, merges agentsmesh.local.yaml if present, and returns a typed ValidatedConfig.
import { loadConfig, type ValidatedConfig } from 'agentsmesh';
const { config, configDir } = await loadConfig(process.cwd());const typed: ValidatedConfig = config;console.log(typed.targets, typed.features);Throws ConfigNotFoundError (code AM_CONFIG_NOT_FOUND) when nothing is found, or ConfigValidationError (code AM_CONFIG_INVALID) with a list of issues when validation fails.
loadConfigFromDirectory(configDir)
Like loadConfig but does not search upward — reads <configDir>/agentsmesh.yaml directly. Used by global scope where the canonical directory is ~/.agentsmesh/.
Loading canonical content
loadCanonical(projectRoot, options?)
Loads canonical content into a typed CanonicalFiles value. When an agentsmesh.yaml can be found, it matches CLI generation by merging extends, installed packs, and local .agentsmesh/ content. Pass a project root (the directory that contains .agentsmesh/) or the canonical directory itself — both shapes are accepted.
import { loadCanonical, type CanonicalFiles, type LoadCanonicalOptions } from 'agentsmesh';
const options: LoadCanonicalOptions = { refreshRemoteCache: false };const canonical: CanonicalFiles = await loadCanonical(process.cwd(), options);console.log(canonical.rules.length, 'rules');console.log(canonical.skills.map((s) => s.name));Pass { config, configDir } when you already loaded config and want to avoid searching again. Set includeExtends: false, or call loadCanonicalFiles(projectRoot), for a local-only .agentsmesh/ read that intentionally skips extends and packs.
Generation
generate(ctx)
Runs the generation pipeline and returns the planned output set. Does not write files — the CLI’s generate command wraps this with the process lock and atomic-write step.
import { generate, type GenerateContext, type GenerateResult,} from 'agentsmesh';
const ctx: GenerateContext = { config, canonical, projectRoot: process.cwd(), scope: 'project', // or 'global' // targetFilter: ['cursor'], // optional whitelist};
const results: GenerateResult[] = await generate(ctx);for (const r of results) { console.log(`${r.status} ${r.target} ${r.path}`);}Each GenerateResult carries target, path (relative to projectRoot), content, currentContent, and status ('created' | 'updated' | 'unchanged' | 'skipped').
resolveOutputCollisions(results)
When two targets emit the same output path with the same content (e.g. Codex CLI and Antigravity both produce AGENTS.md), this collapses them into one result. The CLI calls it automatically; expose it for tools that compose multiple generate() runs.
Import
importFrom(target, opts)
Imports a tool’s native config back into canonical form on disk under .agentsmesh/. Returns one ImportResult per file imported.
import { importFrom, type ImportResult } from 'agentsmesh';
const results: ImportResult[] = await importFrom('claude-code', { root: process.cwd(), scope: 'project', // or 'global' for ~/.claude/});
for (const r of results) { console.log(`imported ${r.fromPath} → ${r.toPath}`);}target can be either a built-in target ID or a registered plugin descriptor ID. Register plugins first with registerTargetDescriptor() or by loading project context for a config that declares plugins.
Lint
lint(opts)
Runs target-specific linters across all enabled targets and returns structured diagnostics. Pure: no I/O, no logging.
import { lint, type LintOptions, type LintResult } from 'agentsmesh';
const result: LintResult = await lint({ config, canonical, projectRoot: process.cwd(), scope: 'project', // targetFilter: ['cursor'], // optional whitelist});
if (result.hasErrors) { for (const d of result.diagnostics.filter((x) => x.level === 'error')) { console.error(`${d.file} (${d.target}): ${d.message}`); } process.exit(1);}LintDiagnostic is { level: 'error' | 'warning'; file: string; target: string; message: string }.
Diff
diff(ctx)
Calls generate(ctx) internally and returns both the raw results and unified diffs against on-disk content. Equivalent to the CLI’s diff command minus the formatting.
import { diff, formatDiffSummary } from 'agentsmesh';
const { results, diffs, summary } = await diff(ctx);
for (const d of diffs) { process.stdout.write(d.patch);}console.log(formatDiffSummary(summary));summary is { new: number; updated: number; unchanged: number; deleted: number }.
computeDiff(results)
Pure helper for callers that already have generate results in hand. Useful when you want to display drift without re-running generation.
import { computeDiff } from 'agentsmesh';
const results = await generate(ctx);const { diffs, summary } = computeDiff(results);Check (lock-sync)
check(opts)
Compares the lock file at <canonicalDir>/.lock against the current canonical state and resolved extends. Pure: no logging, no exit codes — returns a structured LockSyncReport.
import { check, type LockSyncReport } from 'agentsmesh';
const report: LockSyncReport = await check({ config, configDir: process.cwd(), canonicalDir: `${process.cwd()}/.agentsmesh`,});
if (!report.hasLock) { console.error('Not initialized — run agentsmesh generate first.'); process.exit(1);}
if (!report.inSync) { console.error('Drift detected:'); for (const p of report.modified) console.error(` modified: ${p}`); for (const p of report.added) console.error(` added: ${p}`); for (const p of report.removed) console.error(` removed: ${p}`); for (const p of report.extendsModified) console.error(` extend changed: ${p}`); process.exit(1);}LockSyncReport.lockedViolations is the subset of changes that violate collaboration.lock_features — useful for distinguishing “drift the team allows” from “drift that should fail CI”.
Plugin registration
registerTargetDescriptor(descriptor)
Registers a custom target at runtime after validating it against the same descriptor schema used for plugin packages. Plugins ship one of these as their default export. See Building Plugins for the full descriptor contract.
import { registerTargetDescriptor, type TargetDescriptor } from 'agentsmesh';
const myDescriptor: TargetDescriptor = { /* ... */ };registerTargetDescriptor(myDescriptor);After registration, the descriptor participates in generate, importFrom, lint, the supported-tools matrix, and global-mode resolution exactly like a built-in.
Catalog inspection
import { getTargetCatalog, getDescriptor, getAllDescriptors } from 'agentsmesh';
const builtins = getTargetCatalog(); // immutable copy of built-in descriptorsconst plugins = getAllDescriptors(); // registered plugin descriptorsconst cursor = getDescriptor('cursor'); // single lookup, builtins or pluginsgetTargetCatalog() returns an immutable copy of the built-in catalog, so consumer mutations cannot corrupt later descriptor lookups. getAllDescriptors() returns the plugin registry. getDescriptor(id) resolves either side.
Error taxonomy
All public errors inherit from AgentsMeshError and carry a stable code field (AgentsMeshErrorCode). Branch on the class or on err.code — never on message strings.
import { AgentsMeshError, ConfigNotFoundError, ConfigValidationError, TargetNotFoundError, ImportError, GenerationError, RemoteFetchError, LockAcquisitionError, FileSystemError, type AgentsMeshErrorCode,} from 'agentsmesh';
try { await loadConfig(process.cwd());} catch (err) { if (err instanceof ConfigNotFoundError) { console.error(`config missing at ${err.path}`); process.exit(2); } if (err instanceof AgentsMeshError) { console.error(`agentsmesh error (${err.code}): ${err.message}`); process.exit(1); } throw err;}| Class | code | Thrown when |
|---|---|---|
ConfigNotFoundError | AM_CONFIG_NOT_FOUND | agentsmesh.yaml is missing. In --global scope the message points at ~/.agentsmesh/agentsmesh.yaml and suggests agentsmesh init --global. |
ConfigValidationError | AM_CONFIG_INVALID | YAML parses but fails Zod validation. Carries issues: readonly string[]. |
TargetNotFoundError | AM_TARGET_NOT_FOUND | A target ID is referenced but neither built-in nor registered. |
ImportError | AM_IMPORT_FAILED | An importer failed to read or parse a target’s native config. |
GenerationError | AM_GENERATION_FAILED | A target’s generator threw, or output collisions could not be resolved. |
RemoteFetchError | AM_REMOTE_FETCH_FAILED | A remote extends: source could not be fetched (network, 404, hash mismatch). |
LockAcquisitionError | AM_LOCK_ACQUISITION_FAILED | Another process holds .agentsmesh/.generate.lock and the retry budget is exhausted. Carries lockPath and holder. |
FileSystemError | AM_FILESYSTEM | Atomic write or directory operation failed. Carries path and optional errnoCode (e.g. 'EISDIR'). |
ConfigNotFoundError’s constructor accepts an optional message override ({ cause?, message? }) so wrappers can supply scope-aware copy without losing the typed class, code, or path. Existing callers that pass only path (and optional cause) are unchanged.
Canonical types
Imported from agentsmesh or agentsmesh/canonical. These mirror the on-disk .agentsmesh/ shape:
import type { CanonicalFiles, CanonicalRule, CanonicalCommand, CanonicalAgent, CanonicalSkill, SkillSupportingFile, Permissions, IgnorePatterns, McpServer, StdioMcpServer, UrlMcpServer, McpConfig, Hooks, HookEntry,} from 'agentsmesh';McpServer is a discriminated union of StdioMcpServer and UrlMcpServer — narrow with the transport field. CanonicalSkill.supportingFiles carries the skill’s full file tree (relativePath, absolutePath, content).
Target-descriptor types
For plugin authors and tools that introspect target capabilities. Imported from agentsmesh or agentsmesh/targets:
import type { TargetDescriptor, TargetLayout, TargetLayoutScope, TargetOutputFamily, TargetPathResolvers, TargetManagedOutputs, TargetLintHooks, FeatureLinter, RuleLinter, ScopeExtrasFn, ImportPathBuilder, GlobalTargetSupport, TargetCapabilities, TargetGenerators,} from 'agentsmesh';The full descriptor contract — including which fields are required vs optional, the globalSupport block shape, and sharedArtifacts semantics — is documented in Building Plugins.
Stability
The exports listed on this page are the supported public API. Anything imported via deep paths (agentsmesh/dist/..., agentsmesh/src/...) is internal and may change without notice.
A formal semver freeze of the entire surface is planned for v1.0. Until then, the project is pre-1.0 and breaking changes follow the changelog. Three CI gates protect the contract on every push:
publint— package metadata sanity (exports ordering,files, module type)@arethetypeswrong/cliwith theesm-onlyprofile — verifies every entrypoint resolves to types under bothnode16 (from ESM)andbundlermodule resolutiontests/consumer-smoke/— packs the tarball, installs it into a throwaway strict-mode TS project, andtsc --noEmits every public symbol — catchesTS7016and type-resolution regressions that packaging-metadata checks miss
You can run the same gates locally with pnpm publint, pnpm attw, and pnpm consumer-smoke. The runtime behaviour is covered by tests/integration/programmatic-api.integration.test.ts, which exercises every function and every error class with strict assertions on every release.