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.

aiman apply

bash
aiman apply [--dry-run]

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.

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

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 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.

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

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
  validated inventory: 5 skills, 1 MCP server

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/ and mcp/ do not block sync unless they conflict by name with files from remote. On conflict, sync stops and lists the problematic files.

Example output — sync

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

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/ and mcp/ 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).

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

Example output

Repository is in sync, inventory is applied:

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

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)

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):

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

Working tree matches origin/main · nothing to publish

aiman doctor

bash
aiman doctor

Diagnoses the local installation:

  • active repository;
  • detected AI environments;
  • adapter paths;
  • inventory item validity;
  • required MCP env variables;
  • remote availability and sync state.

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: name does not match filename
• 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 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.

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, normalizes it, and writes it as skills/<name>.md in the active inventory repository.

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.
  • --overwrite — overwrite an existing skill on content conflict.

Skill sources by AI environment:

AI environmentIdentifierSkills path
Claude Codeclaude-code~/.claude/skills/*.md and ~/.claude/skills/<name>/SKILL.md
Codex CLIcodex~/.codex/skills/<name>/SKILL.md
CursorcursorNo native skill concept — always returns an empty list

Behavior:

  • if the skill is not found in the specified environment — returns an error;
  • if skills/<name>.md already exists with the same content — succeeds with no changes;
  • if skills/<name>.md already exists with different content and --overwrite is not set — returns an error with a hint to use --overwrite.

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.
  • --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.
  • 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>.
  • 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)

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 loadouts and a complete inventory view are implemented.

Released under the Apache 2.0 License.