Skip to content

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

SubpathUse when
agentsmeshFull surface in one import — runtime functions, error classes, canonical types, target-descriptor types.
agentsmesh/engineGeneration, import, lint, diff, check + error taxonomy + loadProjectContext + config loaders + the types those functions need.
agentsmesh/canonicalloadCanonical, loadCanonicalFiles, LoadCanonicalOptions, and the canonical domain types.
agentsmesh/targetsBuilt-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 descriptors
const plugins = getAllDescriptors(); // registered plugin descriptors
const cursor = getDescriptor('cursor'); // single lookup, builtins or plugins

getTargetCatalog() 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;
}
ClasscodeThrown when
ConfigNotFoundErrorAM_CONFIG_NOT_FOUNDagentsmesh.yaml is missing. In --global scope the message points at ~/.agentsmesh/agentsmesh.yaml and suggests agentsmesh init --global.
ConfigValidationErrorAM_CONFIG_INVALIDYAML parses but fails Zod validation. Carries issues: readonly string[].
TargetNotFoundErrorAM_TARGET_NOT_FOUNDA target ID is referenced but neither built-in nor registered.
ImportErrorAM_IMPORT_FAILEDAn importer failed to read or parse a target’s native config.
GenerationErrorAM_GENERATION_FAILEDA target’s generator threw, or output collisions could not be resolved.
RemoteFetchErrorAM_REMOTE_FETCH_FAILEDA remote extends: source could not be fetched (network, 404, hash mismatch).
LockAcquisitionErrorAM_LOCK_ACQUISITION_FAILEDAnother process holds .agentsmesh/.generate.lock and the retry budget is exhausted. Carries lockPath and holder.
FileSystemErrorAM_FILESYSTEMAtomic 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/cli with the esm-only profile — verifies every entrypoint resolves to types under both node16 (from ESM) and bundler module resolution
  • tests/consumer-smoke/ — packs the tarball, installs it into a throwaway strict-mode TS project, and tsc --noEmits every public symbol — catches TS7016 and 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.