Skip to content

CLI Reference

All commands are provided by the aiman binary.

Output format is unstable before version 1.0. Human-readable command output is not a stable API. For automation, wait for the upcoming --json flag. All format changes are marked breaking: in the changelog.

Global flags

FlagDescription
--helpShow help for a command
--versionPrint the binary version

aiman init

bash
aiman init <repo-url> [--path <dir>]

Connects an inventory repository.

Arguments:

  • <repo-url> — URL of the inventory Git repository.

Flags:

  • --path <dir> — local path for cloning or registering the repository.

Behavior:

  • clones or registers the inventory repository;
  • creates the base structure for an empty repository;
  • creates a local aim.local.yaml;
  • writes the active repository to the global config;
  • classifies existing files as adoptable, existing AIM, or conflicting.

aiman switch

bash
aiman switch <path>

Switches the active inventory repository without cloning.

Use this if you have multiple local inventory repositories or need to run commands from a different directory.

switch unconditionally clears the active pin (aiman apply --loadout <name> --pin, see Pinning the active loadout) — even if the new repository has a loadout with the same name. If you need the pin after switching, set it again: aiman apply --loadout <name> --pin.

aiman apply

bash
aiman apply [--dry-run] [--loadout <name> [--pin]] [--default] [--unpin]

Applies the current local inventory to AI environments without any Git operations.

Flags:

  • --dry-run — show the sha256 delta between the local inventory and the installed AI environments without writing files.
  • --loadout <name> — apply a named loadout declaratively: environments are brought to exactly its set, within the AIM-managed namespace. See aiman apply --loadout.
  • --pin — requires --loadout <name>; after a successful apply, saves that loadout as the active pin so that aiman sync applies it instead of the full inventory going forward. See Pinning the active loadout.
  • --default — apply the full inventory (same as a plain apply with no flags) and clear the active pin, if one is set.
  • --unpin — clear the active pin without applying anything. Does not read the inventory or touch AI environments; must be used on its own, not combined with --loadout, --pin, or --default. With --dry-run it only shows what would be cleared.

apply does not commit, does not push or fetch, and does not update published_hash or synced_hash.

Without --loadout, the command is additive: it installs and updates inventory items (A/M), but never removes anything from environments — even if the repository has a loadouts/ directory.

Example output — apply

On success, apply prints a result line. If any AI environment has changes, a change delta block follows — indented by two spaces, with A (new) and M (modified) markers:

text
applied: 21 skills, 1 MCP server → 3 environments
  A skills/refactor-helper.md   (new in all environments)
  M skills/commit-message.md    (updated in claude-code, cursor)

The counters on the result line (21 skills, 1 MCP server) represent the operation volume: how many items were applied in total. The block below is the change delta: what was actually added or overwritten. These are different numbers — 21 skills means 21 skills total, not 21 changes.

If nothing changed in any environment, only the result line is printed:

text
applied: 21 skills, 1 MCP server → 3 environments

Example output — apply --dry-run

Inventory matches the installed AI environments:

text
[dry-run] nothing to apply — environments match local inventory

Changes detected:

text
[dry-run] would apply 2 changes to 2 environments (claude-code, cursor):
  A skills/new-skill.md   (new in all environments)
  M skills/existing.md   (differs in cursor)
[dry-run] MCP context7 → claude-code, cursor

For MCP servers, apply --dry-run shows only the target AI environments and does not compute a delta — AIM does not compare the installed MCP configuration against the inventory descriptor. See Known limitation: MCP config delta.

If the change list exceeds 20 lines, it is truncated:

text
  A skills/skill-1.md
  A skills/skill-2.md
  … and 15 more

Known limitation: MCP config delta

For skills, AIM computes a delta: it compares the inventory content against the installed file in each AI environment and marks changes with A/M.

For MCP servers, delta is not yet computed. AI environments write MCP entries into shared config files (~/.claude.json, mcp.json, config.toml). Comparing the installed fragment against the inventory descriptor requires parsing and normalizing those configs — this is deferred. As a result, MCP entries in the output show only the target environments, without an A/M marker.

The absence of a marker for an MCP server does not mean it is unchanged. It means AIM does not check its state in the environments. For skills, that claim is verified by the delta; for MCP servers, it is not.

aiman apply --loadout <name>

bash
aiman apply --loadout <name> [--dry-run]

Applies a named loadout — a subset of the inventory described in loadouts/<name>.yamldeclaratively: each AI environment is brought to exactly the loadout's set, within the AIM-managed namespace.

The AIM-managed namespace is everything that exists in the inventory as skills/<name> or mcp/<name>. Every item falls into one of three branches:

ItemAction
in the loadout and allowed in this environment by targets (loadout-level and item-level)install / update
in the inventory, but not in this environment's desired set, and present thereremove from the environment
not in the inventory (created by hand or by another tool)leave untouched

This holds for both skills and MCP servers: the desired set is computed per environment, and each item's item-level targets narrows it symmetrically for both types.

Removing items outside the loadout is the default behavior, not an opt-in flag. The namespace boundary is the guarantee: AIM removes only what it considers its own. Files outside the inventory are never touched.

Name resolution. <name> is resolved in this order: the exact filename loadouts/<name>.yaml, then the normalized name (spaces → hyphens, lowercase), then the loadout whose name field matches the request after normalization. That is why aiman apply --loadout "Documentation Work" finds loadouts/documentation-work.yaml.

targets. The loadout-level targets restricts which environments the loadout may be applied to: environments outside the list are not part of the plan and are not touched. Each item (skill or MCP server) is additionally restricted by its item-level targets — an item is applied to an environment only where both restrictions overlap. For an MCP Item, item-level targets is required; for a Skill Item it is optional — an absent or empty item-level list does not narrow the intersection, so the item passes through into any environment allowed at the loadout level.

The two levels behave differently. An environment cut off by the loadout-level targets drops out of the plan entirely. An environment that the loadout allows but that an item does not list in its item-level targets stays in the plan — and that skill or MCP server will be removed from it if installed there, because it is not part of the desired set for that environment. This rule applies only on this declarative path (apply --loadout, pinned sync); regular apply/sync never removes anything. See the Inventory Format Reference for details.

Example output — apply --loadout

The result line names the loadout, and the counters represent the operation volume: how many loadout items were applied, not how many changes occurred. The delta block uses three markers: A (installed), M (updated), D (removed from the environment):

text
applied loadout "Documentation Work": 2 skills, 1 MCP server → 1 environment
  A skills/create-spec.md   (new in all environments)
  D skills/review-code.md   (removed from all environments)
  M skills/wpage.md   (updated in all environments)
  A mcp/context7.yaml   (new in all environments)

Here the loadout consists of two skills and one MCP server (hence 2 skills, 1 MCP server), while the block has four lines: three skill changes, including the removal of review-code — an inventory skill outside the loadout — plus the installation of the MCP server.

The block lines are sorted: skills by name first, then MCP servers by name. The all environments note means "all environments in the plan" — that is, all detected environments allowed by the loadout's targets. If an action affects only some of them, their names are listed instead, separated by commas.

The D category exists only on the loadout path. A plain apply remains additive.

If the environments already match the loadout, only the result line is printed, without the block.

If a loadout item references an inventory item that no longer exists or is currently invalid, the item is skipped with a warning — it is neither installed nor removed:

text
warning: loadout "dev": no valid inventory item for skill:ghost (skipped)

Example output — apply --loadout --dry-run

--dry-run shows the full A/M/D plan without writing to environments or configs:

text
[dry-run] would apply loadout "Documentation Work" — 4 changes to 1 environment (claude-code):
  A skills/create-spec.md   (new in all environments)
  D skills/review-code.md   (would remove from all environments)
  M skills/wpage.md   (differs in all environments)
  A mcp/context7.yaml   (new in all environments)

Environments already match the loadout:

text
[dry-run] nothing to apply — environments match loadout "Documentation Work"

Always run --dry-run before applying a loadout for the first time: the D lines show exactly what will be removed (see limitations).

Errors — apply --loadout

Loadout not found (loadouts/ contains neither a matching file nor a matching name field):

text
hint: available loadouts: dev, docs
error: loadout "ghost" not found in loadouts/

The hint with the list is printed when there are between one and five valid loadouts: with none there is nothing to suggest, and with more the list is not useful. The hint lists name field values, not filenames.

Loadout found but invalid — apply stops at the first error:

text
error: loadout "empty": items: cannot be empty

To get the full list of errors across all loadout files at once, run aiman push.

Pinning the active loadout: --pin, --default, --unpin

bash
aiman apply --loadout <name> --pin
aiman apply --default
aiman apply --unpin

A pin is persisted state, not a one-off action: it lives in the global user config (~/.config/aim/config.yaml, field loadout — see the Config Reference) and survives CLI restarts and inventory updates. While a pin is set, aiman sync applies that loadout instead of the full inventory — so you can pull updates from remote while staying in a narrow set. For the difference between a one-off apply --loadout <name> and a pin, see Concepts; for the usage scenario, see Workflows.

bash
aiman apply --loadout documentation-work --pin   # apply it and save it as the active pin
aiman apply --default                            # go back to the full inventory, clear the pin
aiman apply --unpin                              # clear the pin without applying anything

The pin is only saved on a successful apply. With --dry-run, the plan for --loadout <name> --pin is printed as usual, but the pin is not saved: dry-run has no side effects, either in environments or in the config. The rule covers all three flags — --default --dry-run and --unpin --dry-run also just show what would happen and leave the pin in place:

text
[dry-run] would unpin loadout "documentation-work" — sync would go back to applying the full inventory

When nothing is pinned:

text
[dry-run] nothing to unpin — no loadout is pinned

--default is the only way to return to the full inventory and clear the pin in a single action. The literal string "Default" is never written as the pin's value: the absence of a pin and Default mode are indistinguishable at the config level.

apply flag conflicts

These errors are checked before the inventory is read and before any changes are made on disk:

CombinationError
--pin without --loadouterror: --pin requires --loadout <name>
--default + --loadouterror: --default and --loadout are mutually exclusive
--default + --pinerror: --pin is redundant with --default
--unpin combined with --loadout, --pin, or --defaulterror: --unpin does not apply — combine only alone

aiman push

bash
aiman push [--dry-run]

Validates the inventory, creates a commit, and pushes changes to the remote.

Flags:

  • --dry-run — show the publish plan without committing or pushing.

push is blocked if the remote is ahead of the local state or if the Git state is unsafe.

Frontmatter validation: if a frontmatter field (other than the file body) contains an error, push prints a warning: to stderr and continues. Errors in the file body are blocking: push prints an error: and aborts.

loadouts/ validation: if the repository has a loadouts/ directory, push validates every loadouts/*.yaml — both the format (name is required, items must not be empty, references must use the skill:/mcp: prefixes) and referential integrity: every skill:<name> must exist as skills/<name>.md or skills/<name>/SKILL.md, and every mcp:<name> as mcp/<name>.yaml. All errors found are printed at once and publishing is blocked:

text
error: loadout "Documentation Work" references unknown skill "missing-skill"
  hint: check loadouts/documentation-work.yaml → items

A missing description and a filename that does not match the normalized name are warnings; they do not block publishing. A repository without loadouts/ is validated exactly as before. The validation also runs in push --dry-run.

After validation, push publishes loadouts/ together with skills/, mcp/, aim.yaml, and .gitignore — no separate git add is needed.

Example output — push

On success, push prints a result line with the published commit hash. If the inventory had changes, a delta block follows — showing which files were published (A for new, M for modified):

text
published: 96e091b · 19 skills, 1 MCP server
  M skills/commit-message.md
  A mcp/jira.yaml
  A loadouts/documentation-work.yaml

If there were no changes, only the result line is printed.

Example output — push --dry-run

Working tree is clean:

text
[dry-run] nothing to publish — working tree is clean

Changes ready to publish:

text
[dry-run] would publish managed changes:
  M skills/review-code.md
  A mcp/jira.yaml
  A loadouts/documentation-work.yaml
  validated inventory: 5 skills, 2 MCP servers

The delta block is built from skills/, mcp/, loadouts/, aim.yaml, and .gitignore. The validated inventory line counts inventory items only — skills and MCP servers.

aiman sync

bash
aiman sync [--dry-run] [--force]

Fetches the published state from the remote and applies it to the local AI environments.

Flags:

  • --dry-run — show the plan without writing to the repository or AI environments;
  • --force — remove untracked files conflicting with remote and apply the published state. Files without conflicts are always preserved.

sync does not perform merges. If the history has diverged, AIM stops and asks you to restore Git manually.

Untracked files in skills/, mcp/, and loadouts/ do not block sync unless they conflict by name with files from remote. On conflict, sync stops and lists the problematic files.

Behavior with an active pin

If a loadout is pinned in the global config (aiman apply --loadout <name> --pin, see Pinning the active loadout), the sync transport itself does not change: fetching and applying the published state to the working tree work exactly as usual. What changes is the install step into AI environments: instead of additively installing the full inventory (A/M, no D), sync declaratively brings environments to exactly the pinned loadout's set — the same way as aiman apply --loadout <name>, including removing items outside the set (D). Without an active pin, the sync behavior described elsewhere in this section is unchanged.

The pin is checked after the working tree is updated to the current published state, so a rename or deletion of the loadout that happened in that very update is detected rather than missed due to stale data. If the pin points to a loadout that no longer exists:

text
error: pinned loadout "documentation-work" not found in inventory

This error blocks the synced_hash update: AI environments stay in the state applied by the previous sync run. The error is distinct from loadout "X" not found in loadouts/, which apply --loadout returns — the wording differs deliberately, so the source of the error is easy to tell apart in logs and in tooling. See the Errors Reference.

In pinned mode, applying loadout "X" (pinned) is always printed before the result line, followed by the apply delta block (A/M/D) in normal output, not only with --dry-run. This is a separate block from the git-transport delta (what changed in the published inventory relative to the previous state) — the apply delta and the transport delta are never mixed into a single block:

text
applying loadout "documentation-work" (pinned)
synced: 67451fc · 2 skills, 1 MCP server → 1 environment
  A skills/create-spec.md   (new in all environments)
  D skills/review-code.md   (removed from all environments)
  M skills/wpage.md   (updated in all environments)
  A mcp/context7.yaml   (new in all environments)

--force applies only to the git transport: it removes untracked files in the inventory repository itself that conflict with remote. It is unrelated to the D lines from the pinned apply, which describe items removed from AI environments. If both happen in the same run, both reports are printed separately.

In pinned mode, sync --dry-run shows the same A/M/D plan as apply --loadout <name> --dry-run, as a separate second block after the regular git delta (or after nothing to sync, if there is nothing to apply from remote):

text
[dry-run] nothing to sync — environments up to date with origin/main
[dry-run] would sync loadout "documentation-work" — 3 changes to 1 environment (claude-code):
  A skills/create-spec.md   (new in all environments)
  D skills/review-code.md   (would remove from all environments)
  M skills/wpage.md   (differs in all environments)

If environments already match the pinned loadout:

text
[dry-run] nothing to sync — environments match loadout "documentation-work"

Example output — sync without a pin

Without an active pin, sync works as before: it additively installs the full inventory without removing anything from environments. The delta block below describes changes to the inventory itself (what arrived from remote), not actions taken in environments.

On success, sync prints a result line with the hash of the applied state. If changes arrived from remote, a delta block follows — showing what changed in the inventory (A for new, M for modified, D for deleted):

text
synced: 67451fc · 21 skills, 1 MCP server → 3 environments
  M skills/commit-message.md
  A skills/refactor-helper.md

As with apply, the counters on the result line represent the operation volume (total applied); the block below is the delta of incoming changes. 21 skills means 21 skills total, not 21 changes.

If the local state already matched origin/main and nothing came in from remote, only the result line is printed:

text
synced: 67451fc · 21 skills, 1 MCP server → 3 environments

With --force, AIM removes conflicting untracked files before applying and reports what was deleted. This report always appears before the result line:

text
discarded untracked files (--force):
  skills/draft.md
  mcp/local-only.yaml
synced: 67451fc · 21 skills, 1 MCP server → 3 environments

The deletion report cannot be suppressed: --force deletes files permanently, and AIM never does this silently.

Example output — sync --dry-run without a pin

Nothing to apply (local state matches origin/main):

text
[dry-run] nothing to sync — environments up to date with origin/main

Changes to apply:

text
[dry-run] would sync 2 changes from origin/main → 3 environments:
  M skills/commit-message.md
  A skills/refactor-helper.md

aiman status

bash
aiman status

Shows the state of the active inventory repository: position relative to origin/main, state of AI environments, and the list of changes in skills/, mcp/, and loadouts/ that have not yet been published.

aiman status fetches from remote. Before calculating the position, the command runs a Git fetch — it answers the question "what in my inventory has changed relative to the published state," and that question inherently includes the remote. Without a fetch, the position would be based on a stale local ref and could be wrong (showing up-to-date when the remote has already moved on).

This is a deliberate difference from git status. git status never touches the network because its question is only about the local working tree. aiman status has a different scope, so the short fetch delay is expected behavior, not a bug.

TODO: add a spinner during fetch

The fetch runs with a hard timeout. If the remote is unreachable, the command does not hang: the Position: field is set to unknown (remote unreachable), a warning: cannot reach remote repository is printed to stderr, and the exit code remains 0.

Position: possible values: up-to-date with origin/main | N commits ahead of origin/main | N commits behind of origin/main | diverged from origin/main (N ahead, M behind) | unknown (remote unreachable).

Environments: possible values: applied (synced <hash>) | unknown | needs sync (N commits not applied).

The Pinned loadout: field shows the active pin (aiman apply --loadout <name> --pin, see Pinning the active loadout) or none if no pin is set. This is local state from the global config — it is always printed, even if the remote is unreachable, and does not require network access.

If the change list exceeds 20 lines, it is truncated with … and N more.

Example output

Repository is in sync, inventory is applied, no pin set:

text
Repository:   git@github.com:you/aim-loadout.git
Position:     up-to-date with origin/main
Environments: applied (synced a1b2c3d)
Pinned loadout: none

Working tree matches origin/main · nothing to publish

An active pin is set:

text
Repository:   git@github.com:you/aim-loadout.git
Position:     up-to-date with origin/main
Environments: applied (synced a1b2c3d)
Pinned loadout: documentation-work

Working tree matches origin/main · nothing to publish

Unpublished changes present:

text
Repository:   git@github.com:you/aim-loadout.git
Position:     2 commits ahead of origin/main
Environments: needs sync (2 commits not applied)
Pinned loadout: none

Changes not yet published (origin/main → working tree):
  A skills/new-skill.md
  M skills/existing.md

  run aiman push to publish
  run aiman sync to apply

Remote unreachable (no network or no access to the repository) — Pinned loadout: is shown as usual, since it is local state:

text
Repository:   git@github.com:you/aim-loadout.git
Position:     unknown (remote unreachable)
Environments: applied (synced a1b2c3d)
Pinned loadout: none

Working tree matches origin/main · nothing to publish

aiman doctor

bash
aiman doctor

Diagnoses the local installation:

  • active repository;
  • detected AI environments;
  • adapter paths;
  • validity of the skills in skills/ of the active inventory repository;
  • required MCP env variables;
  • remote availability and sync state.

The === AI Environments === section answers a single question: was the environment's base directory found. The === Skills === section counts valid and invalid skills in skills/ of the active inventory repository — it does not scan the AI environments and does not show what is installed in them. This holds for every environment: doctor never inspects the set of skills installed in an AI environment.

Example output

Everything is healthy, all environments found:

text
Active Repo: /home/user/.aim/aim-loadout (from /home/user/.config/aim/config.yaml)

=== AI Environments ===
✓ claude-code   /home/user/.claude   found
✓ cursor        /home/user/.cursor   found
✗ codex         /home/user/.codex    not found

=== Skills ===
Found: 4 valid, 0 invalid

=== Sync State ===
synced_hash:    a1b2c3d
published_hash: a1b2c3d
remote HEAD:    a1b2c3d
status:         up-to-date

=== MCP Environment Variables ===
✓ context7 › UPSTASH_REDIS_REST_URL   — set

=== Issues ===
• codex: not installed or not found at /home/user/.codex

Issues present — AI environment not found, required variable not set:

text
Active Repo: /home/user/.aim/aim-loadout (from /home/user/.config/aim/config.yaml)

=== AI Environments ===
✓ claude-code   /home/user/.claude   found
✗ cursor        /home/user/.cursor   not found
✗ codex         /home/user/.codex    not found

=== Skills ===
Found: 2 valid, 1 invalid

=== Sync State ===
synced_hash:    not set
published_hash: not set
remote HEAD:    a1b2c3d
status:         not yet synced

=== MCP Environment Variables ===
✗ context7 › UPSTASH_REDIS_REST_URL   — missing (required)

=== Issues ===
• cursor: not installed or not found at /home/user/.cursor
• codex: not installed or not found at /home/user/.codex
• my-skill.md: invalid: missing description
• context7 › UPSTASH_REDIS_REST_URL — missing (required)

aiman add

bash
aiman add skill <file|dir|-> [--name <string>] [--overwrite]
aiman add mcp <file|-> [--name <string>] [--overwrite]

Adds an inventory item (skill or MCP server) to the local repository from a file, a directory, or stdin.

aiman add skill

bash
aiman add skill <file|dir|->
aiman add skill ~/Downloads/my-skill.md
aiman add skill -

Reads a skill from a file, a directory, or stdin (-), validates the frontmatter, determines the name, and writes the result to the active inventory repository.

Arguments:

  • <file|dir|-> — path to the skill file, path to a folder skill (or its SKILL.md), or - to read from stdin.

Flags:

  • --name <string> — override the skill name; if not set, the name is taken from the file's frontmatter (for a flat skill) or from the directory name (for a folder skill).
  • --overwrite — overwrite an existing skill on content conflict.

Behavior:

  • a single file (not named SKILL.md) or stdin — written as a flat skills/<name>.md;
  • a path to a directory, or a path to a SKILL.md file inside it — recognized as a folder skill and copied in full to skills/<name>/ (SKILL.md plus all reference files alongside it). Both path forms produce the same result;
  • if the directory does not contain SKILL.md — returns a clear error instead of a system-level "is a directory" message;
  • if skills/<name>.md (flat) or skills/<name>/SKILL.md (folder) already exists with the same content — the command succeeds with no changes; for a folder skill, only SKILL.md content is compared, reference files are not. If only the reference files changed, the command reports already identical and does not copy them — --overwrite does not help here, so copy the changed files by hand;
  • file permissions are not preserved during the copy: every file is written as 0644, and the executable bit on scripts inside a folder skill is lost;
  • if the destination already exists with different content and --overwrite is not set — returns an error with a hint to use --overwrite.

Add from a file:

bash
aiman add skill ~/Downloads/create-spec.md

Add from stdin:

bash
cat ~/Downloads/create-spec.md | aiman add skill -

Add a folder skill (both forms are equivalent):

bash
aiman add skill ~/Downloads/get-team-tasks
aiman add skill ~/Downloads/get-team-tasks/SKILL.md

Output on successful add:

text
added: skill create-spec

Output when the skill is already in the inventory with identical content (no-op):

text
up to date: skill create-spec · already identical

aiman add mcp

bash
aiman add mcp <file|->
aiman add mcp jira.yaml
aiman add mcp -

Reads an MCP server description from a YAML file or stdin (-), applies env-strip, and writes the result to the active inventory repository.

Arguments:

  • <file|-> — path to the MCP server YAML file, or - to read from stdin.

Flags:

  • --name <string> — override the server name; if not set, the name is taken from the file's name field.
  • --overwrite — overwrite an existing MCP server on content conflict.

Behavior:

  • if mcp/<name>.yaml already exists with the same content — the command succeeds with no changes;
  • if mcp/<name>.yaml already exists with different content and --overwrite is not set — returns an error with a hint to use --overwrite.

Env-strip: env fields with a populated value are not saved in mcp/<name>.yaml. Instead, the actual values are written to aim.local.yaml under the key mcp_env: {<name>.<VAR>: value}. The inventory stores only descriptors: name, description, required, example. Since aim.local.yaml is excluded from Git, secrets never enter the repository.

Example input file:

yaml
name: jira
description: Jira MCP server
command: npx
args: [-y, mcp-jira]
targets: [claude-code]
env:
  - name: JIRA_API_KEY
    description: Jira API key
    required: true
    value: "secret123"

After the command:

  • mcp/jira.yaml contains the descriptor without the value field;
  • aim.local.yaml contains mcp_env: {jira.JIRA_API_KEY: "secret123"}.

Add from a file:

bash
aiman add mcp jira.yaml

Add from stdin:

bash
cat jira.yaml | aiman add mcp -

Override the server name:

bash
aiman add mcp jira.yaml --name jira-work

Overwrite on conflict:

bash
aiman add mcp jira.yaml --overwrite

Output on successful add:

text
added: mcp jira

Output when the MCP server is already in the inventory with identical content (no-op):

text
up to date: mcp jira · already identical

aiman import

bash
aiman import skill <name> --from <env> [--print] [--overwrite]
aiman import mcp <name> --from <env> [--print] [--overwrite] [--targets all]

Imports an inventory item (skill or MCP server) from an installed AI environment into the local repository.

Requirements shared by both subcommands:

  • An active inventory repository is required. If none is selected (neither aiman init nor aiman switch has been run), the command fails before anything is written to disk — it never writes to the current directory:

    text
    error: no active inventory repository; run 'aiman init' first

    The requirement also applies with the --print flag.

  • The item name is validated before the write path is built. An empty name, ., .., names containing / or \, absolute paths, control characters, and names longer than 255 characters are rejected:

    text
    error: invalid item name "../evil": must not contain "/" or "\"

aiman import skill

bash
aiman import skill <name> --from <env>
aiman import skill create-spec --from claude-code
aiman import skill create-spec --from claude-code --print

Reads a skill by name from the specified AI environment and writes it into the active inventory repository.

AI environments store skills in folder format — <name>/SKILL.md alongside its resource files (references/, scripts). Such a skill is transferred in full: SKILL.md and every resource file end up in skills/<name>/. A flat <name>.md file, if the environment has one, is written as skills/<name>.md.

Arguments:

  • <name> — name of the skill to import.

Flags:

  • --from <env> — import source (required). Accepted values: claude-code, cursor, codex.
  • --print — print the skill content to stdout without writing to disk. For a folder skill, only SKILL.md is printed; resource files are not.
  • --overwrite — overwrite an existing skill on content conflict.
  • --targets — accepted for symmetry with aiman import mcp, but ignored for skills: a skill's targets come only from its frontmatter, see Concepts.

Skill sources by AI environment:

AI environmentIdentifierSkills path
Claude Codeclaude-code~/.claude/skills/<name>/SKILL.md and flat ~/.claude/skills/*.md
Codex CLIcodex~/.codex/skills/<name>/SKILL.md
Cursorcursor~/.cursor/skills/<name>/SKILL.md

Behavior:

  • if the skill is not found in the specified environment — returns an error;
  • if the skill already exists in the inventory with the same content — succeeds with no changes;
  • if the skill already exists in the inventory with different content and --overwrite is not set — returns an error with a hint to use --overwrite;
  • a content match is determined by SKILL.md; resource files are not taken into account. If only the resource files changed in the environment while SKILL.md stayed the same, the command reports already identical and does not carry the changes over — --overwrite does not help here either, so copy the changed files by hand.

Limitation: file permissions are not preserved — every file is written as 0644, both when transferring into the inventory and when installing into an AI environment later. The executable bit on scripts inside a folder skill is lost; see Troubleshooting for how to restore it.

Import from Claude Code:

bash
aiman import skill create-spec --from claude-code

Output on successful import:

text
imported: skill create-spec · from claude-code

Output when the skill is already in the inventory with identical content (no-op):

text
up to date: skill create-spec · already identical

Preview without writing:

bash
aiman import skill create-spec --from claude-code --print

aiman import mcp

bash
aiman import mcp <name> --from <env> [--print] [--overwrite] [--targets all]
aiman import mcp context7 --from claude-code
aiman import mcp jira --from cursor --print
aiman import mcp context7 --from claude-code --targets all

Scans the live configuration of the specified AI environment, locates MCP servers, applies env-strip, and writes the descriptor to the active inventory repository.

Arguments:

  • <name> — name of the MCP server to import.

Flags:

  • --from <env> — import source (required). Accepted values: claude-code, cursor, codex.
  • --print — print the YAML descriptor to stdout without writing files. The conflict check runs before printing: if mcp/<name>.yaml already exists with different content, the command fails and prints nothing — add --overwrite to see the descriptor anyway (the file is still not overwritten).
  • --overwrite — overwrite the existing file if content differs.
  • --targets all — set all three adapters (claude-code, cursor, codex) as target environments in the descriptor. By default, only the source environment is used.

Behavior:

  • Env-strip: real env variable values are not saved in mcp/<name>.yaml. Instead, they are written to aim.local.yaml under the key mcp_env: {<name>.<VAR>: value}. Only descriptors — name, required — go into the inventory. Since aim.local.yaml is excluded from Git, secrets never enter the repository.

  • Deterministic env order: env variable descriptors are written sorted by name. Re-importing an unchanged source produces byte-identical YAML, so already identical triggers reliably and Git sees no diffs caused by reordered lines.

  • stdio transport only: a server declared with "type": "http", "type": "sse", or a bare "url" without a command is not imported. The command fails with an error naming the server and the reason, and writes nothing to disk:

    text
    error: MCP server "remote-tool": unsupported transport "http" (only stdio servers can be imported)

    Other servers in the same config are imported as usual.

  • Deduplication: if the same server name appears multiple times with the same command and arguments, the first entry is used.

  • Ambiguity (AmbiguousError): if the same server name appears with different commands or arguments, the command fails — you must explicitly specify the source via --from.

  • Server not found: if no server with the given name exists in the environment config, the command fails with MCP server "<name>" not found in <env>. A server with an unsupported transport does not fall into this category — it produces the error naming the reason (see above) rather than not found.

  • Unknown environment: if the --from value is not recognized, the command fails with unknown environment: X; available: claude-code, cursor, codex.

  • if mcp/<name>.yaml already exists with the same content — succeeds with no changes;

  • if mcp/<name>.yaml already exists with different content and --overwrite is not set — returns an error with a hint to use --overwrite.

MCP configuration sources by AI environment:

AI environmentIdentifierConfiguration file
Claude Codeclaude-code~/.claude.json (key mcpServers)
Cursorcursor~/.cursor/mcp.json (key mcpServers)
Codex CLIcodex~/.codex/config.toml (section mcp_servers)

For Claude Code, ~/.claude/settings.json is read as well: a server is taken from there only if no entry with that name exists in ~/.claude.json.

Import from Claude Code:

bash
aiman import mcp context7 --from claude-code

Output on successful import:

text
imported: mcp context7 · from claude-code

If the MCP server had env variables with populated values, they are written to aim.local.yaml:

text
imported: mcp context7 · from claude-code · secrets stored in aim.local.yaml

Output when the MCP server is already in the inventory with identical content (no-op):

text
up to date: mcp context7 · already identical

Preview the YAML descriptor without writing:

bash
aiman import mcp context7 --from claude-code --print

Import with all adapters set as targets:

bash
aiman import mcp context7 --from claude-code --targets all

Overwrite if the file already exists:

bash
aiman import mcp context7 --from claude-code --overwrite

Commands outside the current public scope

aiman list may be present in the codebase as a historical command, but its full public contract is deferred until a complete inventory view is implemented.

Released under the Apache 2.0 License.