npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cmaform

v0.4.0

Published

Terraform-style declarative management for Claude Managed Agents and related resources.

Readme

cmaform is a Terraform-style CLI for managing Claude Managed Agents and their surrounding resources — agents, skills, memory stores, environments, vaults, and deployments — as files in your repo. You declare each resource as a YAML file, run cmaform plan to see what will change, and cmaform apply to ship it.

  [~] update agent       "release-prep" (id=agent_01Qx..., version=6)
       file: agents/release-prep.yaml
       ~ system:
           ... (12 unchanged lines)
         - prior wording...
         + revised wording...
           ... (8 unchanged lines)
  [+] create skill       "spec-lookup"
       dir:  skills/spec-lookup
       hash: 7b8b14094e01...

Plan (agents):         0 to add, 1 to change, 0 to archive, 5 unchanged.
Plan (skills):         1 to add, 0 to change, 0 to delete, 0 unchanged.
Plan (memory_stores):  0 to add, 0 to change, 0 to archive, 0 unchanged.
Plan (environments):   0 to add, 0 to change, 0 to archive, 1 unchanged.
Plan (vaults):         0 to archive, 1 unchanged.
Plan (deployments):    0 to add, 0 to change, 0 to archive, 1 unchanged.

⚡ Quick Start

Try it first

npx cmaform --help

Install and use

npm install -g cmaform

Bootstrap a new config directory and import an existing managed agent

export ANTHROPIC_API_KEY=sk-ant-...

mkdir my-agents && cd my-agents
cmaform pull agent_011CaSWcCrMdQdp4SA6TVdH6   # writes agents/<name>.yaml + state
cmaform plan                                  # show the diff
cmaform apply                                 # confirm → push to Anthropic

Claude Managed Agents and related APIs for Skills / Memory Stores / Environments / Vaults / Deployments are currently beta. cmaform calls @anthropic-ai/sdk's beta.agents.* / beta.skills.* / beta.memoryStores.* / beta.environments.* / beta.vaults.* / beta.deployments.* endpoints directly.

🚀 Usage

Commands

| Command | What it does | | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | cmaform pull <id> [--by-id] | Import a remote resource by ID (agent_* / skill_* / memstore_* / env_* / vlt_* / deploy_*) into local files + state. Pass --by-id to keep raw IDs in the written agent / deployment YAML instead of rewriting refs to the name form | | cmaform plan [--verbose\|-v] [target...] | Diff local YAML / state / remote and print a Terraform-style plan. Requires an existing cmaform.state.json from init or pull | | cmaform apply [--yes\|-y] [--verbose\|-v] [target...] | Show plan, prompt for confirmation, apply, save state. Requires an existing cmaform.state.json from init or pull | | cmaform sync [--by-id] | Re-fetch every entry in state from remote and rewrite local YAML. Pass --by-id to keep raw IDs in the written agent YAML instead of rewriting refs to the name form | | cmaform init | Initialize / reconcile the state file against remote (no remote writes; spirit of terraform init) | | cmaform list | Show local files / state / remote side-by-side | | cmaform fmt | Rewrite multiagent.agents[].id / skills[].skill_id in local YAML to the name form, using cmaform.state.json for the id → name lookup |

Filtering plan / apply

The last positional arguments to plan and apply are targets. A target can be a resource kind or an individual resource name — useful for staged rollouts (e.g. create a skill first, copy its skill_id into an agent YAML, then apply the agent).

cmaform apply skills                     # all skills
cmaform apply agents                     # all agents
cmaform apply slack-mention-lookup       # a single skill by name
cmaform apply release-prep --yes         # a single agent, skip confirmation
cmaform apply skills release-prep        # all skills + one agent

A <target> is either a kind alias (matches every resource of that type) or the individual name of one specific resource. Use the table below for kind aliases; anything else is treated as a resource name.

| Resource | Kind aliases | Individual name format | Example | | ------------ | ----------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------- | | Agent | agent / agents | name: field in the YAML | release-prep (agents/release-prep.yaml) | | Skill | skill / skills | directory name under skills/ | slack-mention-lookup (skills/slack-mention-lookup/) | | Memory Store | memory_store / memory_stores / memstore / memstores | directory name under memory_stores/ | team-notes (memory_stores/team-notes/) | | Environment | environment / environments / env / envs | directory name under environments/ | python-dev (environments/python-dev/) | | Vault | vault / vaults | directory name under vaults/ | my-bot (vaults/my-bot/) | | Deployment | deployment / deployments / deploy / deploys | directory name under deployments/ | nightly (deployments/nightly/) |

plan expands create / update diffs symmetrically: a new resource is rendered as + field: ... blocks just like an update is rendered as ~ field: ... blocks. Long string fields (system, description) are truncated to 3 lines plus an ... (N lines hidden) marker. Pass --verbose to show full content.

If you pass an individual name that doesn't exist anywhere (local YAML, state, or remote), cmaform exits with code 2. An unmatched kind is fine — it just shows 0 to add, 0 to change, ....

Pulling existing resources

cmaform pull agent_011CaSWcCrMdQdp4SA6TVdH6   # writes agents/<name>.yaml
cmaform pull skill_013uPS15B3Kw82NpjH4uNQep   # state only — SKILL.md is not regenerated
cmaform pull memstore_01ABC...                # writes memory_stores/<name>/manifest.yaml
cmaform pull env_015G...                      # writes environments/<name>/manifest.yaml
cmaform pull vlt_011CaQ...                    # writes vaults/<name>/manifest.yaml (credentials not managed yet)
cmaform pull deploy_01Gk...                   # writes deployments/<name>/manifest.yaml + state

Skill content is not returned by the Anthropic API once uploaded, so pull for skills only records the ID + version + display title into state. The local SKILL.md must be authored by you.

📦 Resources

cmaform manages six resource types — agents, skills, memory stores, environments, vaults, and deployments. Each lives under its own top-level directory beneath the config root. (Create vaults with cmaform, then finish configuring them on the Anthropic Console.)

Agent (agents/<name>.yaml)

name: my-agent # unique within the workspace — this is the identity key
model:
  id: claude-sonnet-4-6
  speed: standard # standard | fast
description: short description
system: |-
  ... full system prompt ...
mcp_servers:
  - name: slack
    type: url
    url: https://mcp.slack.com/mcp
tools:
  - type: agent_toolset_20260401
    default_config:
      enabled: true
      permission_policy:
        type: always_allow
    configs: []
skills: []
metadata: {}
  • Identity: the name field. agent_id lives in state, not YAML.
  • Diff: deep-equal on name / model / description / system / tools / mcp_servers / skills / multiagent / metadata.
  • Delete: present in state but missing locally ⇒ archive (reversible).
  • Arrays (tools, mcp_servers, skills) are fully replaced — local is the source of truth.

See the Anthropic Agent Setup docs for the full schema.

Skill (skills/<localName>/)

Each skill is a directory. The Anthropic API uploads a folder containing SKILL.md plus any auxiliary files.

skills/<localName>/
├── SKILL.md            # required (YAML frontmatter + markdown body)
├── REFERENCE.md        # optional
└── scripts/
    └── helper.py       # optional

SKILL.md requires frontmatter:

---
name: my-skill
description: What this skill does, and when Claude should use it.
---

# My Skill

...
  • name: ≤64 chars, [a-z0-9-] only. anthropic / claude are reserved.
  • description: ≤1024 chars.

cmaform computes a SHA-256 hash of the entire directory and compares it against the hash stored in state. If they differ, apply uploads a new version of the skill.

Referencing a skill from an agent

# agents/foo.yaml
skills:
  - type: anthropic
    skill_id: xlsx # well-known Anthropic skills stay id-based
  - type: custom
    name: slack-mention-lookup # = local directory name under skills/
    version: latest # optional

The raw-ID form (skill_id: skill_01XXXXXX) is also accepted for type: customname and skill_id are interchangeable. See Name-based references below for resolution semantics.

⚠️ Skills cannot be archived. Deleting the directory and running apply permanently deletes the skill and all of its versions.

🔗 Name-based references

multiagent.agents[] and skills[] accept references by logical name in addition to raw IDs. Names are resolved at plan / apply time, so YAML can reference resources without committing any workspace-specific IDs.

# agents/coordinator.yaml
multiagent:
  type: coordinator
  agents:
    - type: agent
      name: spec-qa # = the `name` field of agents/spec-qa.yaml
    - type: agent
      name: release-prep
      version: latest # optional
skills:
  - type: custom
    name: slack-mention-lookup # = the directory name under skills/

Resolution order for each name:

  1. cmaform.state.json — if the resource is already tracked locally, use its ID.
  2. Remote — findAgentByName / findSkillByDisplayTitle (cached per run).
  3. Local apply set — if a YAML for that name exists locally and is being created in this run, the reference is treated as a forward dependency. cmaform substitutes a placeholder during plan and replaces it with the real ID right after the dependency is created.

If none of the above match, plan / apply fails with an error pointing at the unresolved name.

pull / sync write back in name form

When writing remote agents back to YAML, cmaform replaces any id / skill_id that resolves to a known local name with the name form. IDs for resources not tracked in state are kept as-is.

Pass --by-id to pull / sync to opt out of the rewrite and keep raw IDs in the written YAML.

Raw-ID form

{ type: agent, id: agent_... } and { type: custom, skill_id: skill_... } are also accepted and behave identically to the name form.

If an entry writes both name: and id: (or skill_id:), cmaform resolves the name and asserts that it matches the pinned ID. Mismatches abort plan / apply with a clear error. This is useful as a safety net while migrating from id-based references — cmaform fmt can then drop the redundant raw IDs once everything verifies.

type: anthropic skills (well-known IDs like xlsx) always use the skill_id form. type: self agent references take neither.

Memory Store (memory_stores/<localName>/manifest.yaml)

name: my-store
description: optional
metadata:
  team: platform
  • Diff fields: name / description / metadata.
  • metadata is patched: keys missing locally are deleted, others are upserted.
  • Delete = archive (one-way; the store's memory data is preserved).

Environment (environments/<localName>/manifest.yaml)

name: python-dev
description: optional
config:
  type: cloud
  packages:
    pip:
      - pandas
      - numpy==2.2.0
    npm:
      - express
  networking:
    type: limited
    allowed_hosts:
      - https://api.example.com
    allow_mcp_servers: true
    allow_package_managers: true
metadata: {}
  • Diff fields: name / description / metadata / config.
  • Only config.type: cloud is currently supported (self-hosted environments are out of scope).
  • Empty package lists, false defaults in limited networking, and the server-side type: 'packages' marker are normalized away so the plan is idempotent.
  • Delete = archive. Archived environments stop accepting new sessions but existing sessions keep working.

Vault (vaults/<localName>/)

Status: ⚠️ Partial support — vault design is still evolving in cmaform.

| Operation | Supported by cmaform | Notes | | ------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Create vault | ✅ | New local manifest → vault is created on the server. | | Archive vault | ✅ | Removing a local manifest archives the vault. Cascades server-side to attached credentials. | | Update vault | ❌ | Edits to display_name / metadata after creation are silently ignored. Archive and recreate to rename / relabel. | | Manage credentials | ❌ | Credentials are not yet managed by cmaform — the secret-resolution design is still being settled. Use the Anthropic Vault Credentials API directly to attach / rotate credentials. |

The vault scope will be expanded (update, credential management with secret backends) in a future release.

A vault is a container that holds credentials for MCP servers. The local layout is intentionally minimal in this release:

vaults/
└── my-bot/
    └── manifest.yaml

manifest.yaml — the vault definition:

display_name: my-bot
metadata:
  external_user_id: bot

Deployment (deployments/<localName>/manifest.yaml)

A deployment binds an agent + environment + initial events + an optional cron schedule into one unit. The platform creates a session from the deployment on each schedule fire (and lets you trigger one on demand). This is how a managed agent runs on a schedule.

name: nightly-triage # unique within the workspace
description: optional
# Reference an agent by name (resolved via state -> remote -> local apply set)
# or by raw id. Omit `version` to track the latest at create time, or pin one.
agent: release-prep
# agent:
#   name: release-prep
#   version: 6
environment: python-dev # environment name or env_... id
# Events sent to each session immediately after creation (1–50).
initial_events:
  - type: user.message
    content:
      - type: text
        text: Run the nightly triage.
# Optional. 5-field POSIX cron (minute hour day-of-month month day-of-week).
# Extended syntax (seconds/year, L W # ?, @daily) is NOT supported. Omit the
# whole block for an on-demand (manual-run) deployment.
schedule:
  type: cron
  expression: '0 9 * * 1-5' # 09:00 on weekdays
  timezone: Asia/Tokyo # IANA timezone
# Optional. Resources mounted into each session's container.
resources: []
# Optional. Vault names or vlt_... ids supplying stored credentials.
vault_ids: []
metadata: {}
  • Identity: the name field. The deployment_id lives in state, not YAML.
  • Diff fields: name / description / agent / environment_id / initial_events / schedule / resources / vault_ids / metadata.
  • References: agent / environment / vault_ids[] accept logical names (resolved at plan/apply time, same rules as Name-based references) or raw ids. A name pointing at a resource being created in the same apply run is treated as a forward dependency — cmaform creates it first, then the deployment.
  • Agent version: omit version to track the latest at create time (version drift is then ignored in the diff). Pin a version to detect and re-pin changes.
  • Delete: present in state but missing locally ⇒ archive (one-way; the schedule stops firing).
  • Out of scope: the imperative pause / unpause / run transitions are not modeled declaratively — status is treated as remote-owned. resources containing write-only credentials (e.g. a GitHub authorization_token) are sent on create/update but stripped before diffing, so they never appear as a permanent change.

See the Anthropic Deployments docs for the full schema.

🗂️ Directory Layout

cmaform reads from the current working directory (or CMAFORM_DIR if set):

<cwd>/
├── agents/
│   └── *.yaml
├── skills/
│   └── <localName>/SKILL.md
├── memory_stores/
│   └── <localName>/manifest.yaml
├── environments/
│   └── <localName>/manifest.yaml
├── vaults/
│   └── <localName>/manifest.yaml
├── deployments/
│   └── <localName>/manifest.yaml
└── cmaform.state.json

🧾 State File (cmaform.state.json)

{
  "agents": {
    "release-prep": { "id": "agent_01Qx...", "version": 6 }
  },
  "skills": {
    "slack-mention-lookup": {
      "id": "skill_013uPS...",
      "version": "1778647403232223",
      "hash": "7b8b14094e01...",
      "display_title": "slack-mention-lookup"
    }
  },
  "memory_stores": {
    "team-notes": { "id": "memstore_01...", "name": "team-notes" }
  },
  "environments": {
    "python-dev": { "id": "env_01...", "name": "python-dev" }
  },
  "vaults": {
    "my-bot": { "id": "vlt_01...", "display_name": "my-bot" }
  },
  "deployments": {
    "nightly-triage": { "id": "deploy_01...", "name": "nightly-triage" }
  }
}
  • Maintained by pull / apply / sync / init.
  • Recommended to .gitignore (treat as the local source of truth, like a Terraform state file).
  • If someone shares a state file with you, run cmaform sync to regenerate the local YAML for every agent (skill bodies cannot be restored — see above).

🔐 Environment Variables

| Variable | Required | Purpose | | ------------------- | -------- | -------------------------------------- | | ANTHROPIC_API_KEY | ✅ | Anthropic API authentication | | CMAFORM_DIR | | Config root directory (default: cwd) |

⚠️ Caveats

  • Renaming an agent creates a new agent. To rename, delete the old YAML, run apply (archives the old one), then add the new YAML with the new name.
  • Deleting a skill directory and running apply is destructive — there is no archive for skills.
  • For full reproducibility, avoid making manual changes in the Anthropic Console. Always go through the YAML / SKILL.md / manifest.yaml.

🛠️ Development

mise install
pnpm install
pnpm dev -- --help     # run from source (tsx)
pnpm typecheck
pnpm build             # bundle to dist/cli.js via tsup
node dist/cli.js --help

📋 Requirements

  • Node.js ≥ 22
  • An Anthropic API key with access to the Claude Managed Agents and related Skills / Memory Stores / Environments / Vaults / Deployments beta APIs

📄 License

MIT