agent-installer is a local TypeScript CLI for installing agent artifacts from a repository into Codex and Claude Code.
It is built for repositories that keep:
- skills in
skills/ - reusable prompt or command files in
prompts/orcommands/
The CLI scans that repository, shows what is available, and installs the selected items into the expected local directories.
- Codex skills
- Claude Code skills
- Claude Code command-style prompts
Current scope:
- local directories or HTTPS Git repositories
- convention-based scanning
- no manifest file
my-agent-repo/
skills/
review/
SKILL.md
engineering/
code-review/
SKILL.md
prompts/
commit-message.md
commands/
release-notes.md
Rules:
- a skill is valid only if its directory contains
SKILL.md - skill directories may be nested below
skills/; the default scan depth is 3 directory levels belowskills/ - a skill's install name is the basename of the directory containing
SKILL.md - duplicate skill names are rejected, even if they appear in different nested categories
- prompt and command files must be Markdown files
prompts/foo.mdandcommands/foo.mdcannot both exist
A skill that declares the Claude Code frontmatter setting disable-model-invocation: true is meant to be invoked
explicitly rather than selected by the model. agent-installer translates that intent for Codex:
| Claude Code source frontmatter | Codex metadata in the managed copy |
|---|---|
disable-model-invocation: true |
agents/openai.yaml with policy.allow_implicit_invocation: false |
---
name: review
disable-model-invocation: true
---
# ReviewBehavior:
- the translation is automatic in every install mode: interactive,
scan,install --all, and remote Git sources - only a literal boolean
truetranslates;"true",1,yes,false, and a missing setting change nothing - the
agents/openai.yamlit writes is a detail of the managed copy under~/.agents; the source repository is never modified - Codex still runs the skill when it is invoked explicitly; only implicit selection is disabled
- there is no CLI flag for the translation, and none is required
- removing the setting from the source removes the generated file on the next update, and uninstalling removes it with the skill
A skill may ship its own agents/openai.yaml. When the Claude setting enables the translation, the managed copy is
that authored file with the invocation policy applied on top:
# skills/review/agents/openai.yaml in the source repository
interface:
arguments:
- name: path
dependencies:
- jq
policy:
allow_implicit_invocation: true# ~/.agents/skills/review/agents/openai.yaml after installation
interface:
arguments:
- name: path
dependencies:
- jq
policy:
allow_implicit_invocation: falseRules:
- authored interface, dependency, and other metadata is retained, including sibling keys under
policyand comments; the file is re-serialized, so its formatting may be normalized even though its content is not - precedence: when the authored Codex policy and the Claude setting disagree,
disable-model-invocation: truewins, because it is the cross-runtime declaration being translated; only the managed copy changes - an authored policy that already disables implicit invocation is kept as is, and a rescan reports
installed-same - an empty
policy:key carries no authored intent, so the translation fills it in - if the translation applies but the authored metadata cannot be parsed as a YAML mapping, the run fails with a
source-configuration error and the managed copy is neither created nor changed; because reconciliation resolves the
translation up front, this aborts
scanas well as an install - when the Claude setting does not enable the translation, authored metadata is copied verbatim and never validated
Install directly from GitHub:
pnpm add -g github:lbacik/agent-installerOr with npm:
npm install -g github:lbacik/agent-installerFor local development, install dependencies:
pnpm installBuild the CLI:
pnpm buildRun it during development:
pnpm devFrom inside a source repository:
agent-installerOr point it at a repository explicitly:
agent-installer /path/to/repoOr point it at an HTTPS Git repository:
agent-installer https://github.com/org/agents.gitEvery install writes the canonical copy under ~/.agents. Nothing is linked into a tool directory until ~/.agents/agent-installer/config.yaml names that destination as a target (create it with agent-installer config init, below). A fresh install with no config.yaml produces base-store-only artifacts: scan reports no exposures and install creates no symlinks.
Interactive install:
agent-installer [path]Install interactively from a remote ref:
agent-installer https://github.com/org/agents.git --ref mainLimit the visible interactive list length:
agent-installer [path] --list-length 12Show scan results only:
agent-installer scan [path]Print scan results as JSON instead:
agent-installer scan [path] --jsonShow scan results for a remote tag, branch, or commit:
agent-installer scan https://github.com/org/agents.git --ref v1.2.0Options such as --ref and --skill-max-depth belong after the subcommand name. An option placed before a
subcommand (agent-installer --ref v1.2.0 scan ...) is rejected with an error rather than silently ignored.
Scan deeper nested skill catalogs:
agent-installer scan [path] --skill-max-depth 5Install or update everything found:
agent-installer install [path] --allBy default, install --all aborts with a non-zero exit and installs nothing if any discovered artifact is a conflict
(a target path exists but is not managed by this installer). Pass --allow-conflicts to install the remaining eligible
artifacts anyway; skipped conflicts are still reported on stderr.
agent-installer install [path] --all --allow-conflictsInstall or update an exact subset by repeating --only <artifact-id> (the same id vocabulary uninstall and list
use, for example skill:review or prompt:commit-message). --only and --all are mutually exclusive, and every
selector must match a discovered artifact or the run aborts with a non-zero exit and installs nothing, naming the
unmatched selectors:
agent-installer install [path] --only skill:review --only prompt:commit-messageSelection is applied to the reconciled set, so conflict handling behaves exactly as it does under --all, including
--allow-conflicts. Unselected artifacts are left untouched.
Remove managed artifacts that the scanned source no longer offers (source-missing), so the base store ends up
containing exactly what the source declares and nothing else:
agent-installer install [path] --all --prune--prune deletes managed files. It removes the base-store copy, every recorded exposure symlink, and the state entry
for every artifact reconciled as source-missing for the scanned source. Pruning is opt-in and off by default: a
skipped artifact is recoverable with a later install, a pruned one is not, and scanning a legitimately narrower
source would otherwise turn a mistyped path into data loss. --prune combines with --only, pruning only what the
source no longer offers; a merely-unselected artifact that the source still discovers is left installed. Pruning is
also scoped to the scanned source identity: artifacts owned by a different source are never touched, and advancing
--ref on a remote source does not prune the previously installed artifacts, since they reconcile as updates instead.
Install or update everything found from a remote ref:
agent-installer install https://github.com/org/agents.git --ref main --allReport the result as JSON instead of the human-readable summary:
agent-installer install [path] --all --jsonRemove managed entries by id:
agent-installer uninstall skill:review prompt:commit-messageInteractively list installed managed entries. Unselect entries to uninstall them:
agent-installer listLimit the visible interactive list length:
agent-installer list --list-length 12Print managed entries as JSON instead of opening the interactive selection UI:
agent-installer list --jsonCreate ~/.agents/agent-installer/config.yaml, prompting for one or more named
exposure targets (each with an optional skills directory and/or prompts
directory). Offers a "claude" preset that prefills ~/.claude/skills and
~/.claude/commands, or a previously detected legacy Claude exposure
directory if one exists. Refuses to overwrite an existing config.yaml unless
you confirm or pass --force:
agent-installer config init
agent-installer config init --forceWhen config.yaml exists, scan, list, install, uninstall, and
interactive mode all validate it at startup and abort with an error naming
the file and the problem if it is malformed. No config.yaml means
base-store-only installation.
Reconcile already-installed artifacts with the current config.yaml without
contacting a source repository. sync creates missing exposures, moves
exposures whose target directory changed, and removes orphaned exposures whose
target was removed or narrowed to no longer declare that kind. Legacy
exposures are never modified, only reported:
agent-installer sync
agent-installer sync --only skill:review --target claude
agent-installer sync --dry-runPass --allow-conflicts to sync the eligible pairs and skip conflicting ones
instead of aborting; add --json for a machine-readable report.
scan, install, and list accept --json. Under --json:
- stdout carries exactly one JSON object and nothing else; all human-readable progress, warnings, and errors move to stderr.
- The object always carries
schemaVersion: 2, so a later shape change cannot break a consumer silently. - Exit statuses are unchanged. A strict-mode abort (an unmanaged conflict without
--allow-conflicts, or an--onlyselector that matches nothing) still exits non-zero, and still prints a JSON object on stdout describing the refusal via anerrorstring. list --jsonprints the managed entries and exits; it never opens the interactive UI, so it produces output with no TTY attached.
Each artifact entry carries: id, kind, name, status, sourceIdentity, relativeSourcePath, basePath,
exposures[] (one entry per desired target-kind pair, plus one per owned legacy exposure), sourceHash,
installedHash, and, when installed from a remote Git ref, requestedRef and resolvedCommit. A conflict
entry also carries conflictReason and conflictPath, which are reserved for base-path conflicts only;
per-exposure conflicts live in that exposure's exposures[] entry.
scan --json:
{
"schemaVersion": 2,
"artifacts": [
{ "id": "skill:review", "kind": "skill", "name": "review", "status": "new", "...": "..." }
]
}artifacts includes source-missing entries: managed artifacts no longer present in the currently scanned source
repository.
install --json reports what happened to each discovered artifact, split into five id-addressable arrays:
{
"schemaVersion": 2,
"installed": [],
"updated": [],
"skipped": [],
"refused": [],
"pruned": []
}installed: artifacts that had never been installed before and now are.updated: previously installed artifacts whose source or exposure had drifted, now reinstalled.skipped: conflicting artifacts left untouched because--allow-conflictswas passed.refused: conflicting artifacts that caused the whole run to abort (no--allow-conflicts);installedandupdatedare empty in that case, and the object carries anerrorstring. A non-conflict abort, such as an unmatched--onlyselector, reports the same empty arrays anderrorstring withrefusedleft empty, since no discovered artifact is implicated.pruned:source-missingartifacts removed because--prunewas passed; always empty without that flag.
list --json reports the currently managed entries without re-scanning the source repository, so status reflects
whether the base-store copy still matches what was installed and whether its recorded exposure symlinks are intact, not
whether the upstream source has since changed:
{
"schemaVersion": 2,
"artifacts": [
{ "id": "skill:review", "kind": "skill", "name": "review", "status": "installed-same", "...": "..." }
]
}- Change into a repository that contains
skills/,prompts/, orcommands/. - Run
agent-installer. - Review the discovered items and their statuses.
- Keep selected items installed, update changed ones, or remove managed items that should no longer remain installed.
For remote HTTPS Git repositories, the CLI uses the installed git command and the user's existing HTTPS credential helpers. Remote checkouts are temporary and are removed after the command completes.
agent-installer can run as a build step of a container image, installing a reviewed skill bundle so it is already
present when the image starts. Every managed path derives from HOME (see resolveTargetPaths in
src/paths.ts), so the supported way to do this is to set HOME to the runtime user's home directory
for the install step:
FROM node:20-slim
RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*
RUN useradd --create-home --home-dir /home/agent agent
RUN npm install -g github:lbacik/agent-installer
# Install as the runtime user's HOME so base-store paths and exposure symlinks
# are correct for the user who will actually run the agent.
RUN HOME=/home/agent agent-installer install https://github.com/org/agents.git --ref main --all \
&& chown -R agent:agent /home/agent/.agents /home/agent/.claude
USER agent
ENV HOME=/home/agentThe installed tree cannot be relocated after the fact. Exposure symlinks are created with an absolute
target pointing at the base-store path under the HOME used during install. Installing into a staging directory and
then copying or moving the result into the final image layer produces symlinks that still point at the staging path,
silently breaking exposure at runtime. Always install directly under the final runtime HOME; if a tree must move,
reinstall after the move instead of relocating it. This constraint, and the rejection of a --target-root flag as an
alternative, is recorded in
docs/adr/0003-home-redirection-for-build-time-install.md.
Prerequisites for a remote install during a build:
- Node satisfying this package's
engines.noderequirement (>=20) git- network access to the source repository
- any HTTPS credentials the source repository needs (the installer uses the system
gitcommand and its existing credential helpers; see Typical Workflow)
If the build step runs as a different user than the runtime user (for example, building as root and running as an
unprivileged user), ensure the runtime user can read the installed tree, as in the chown step above.
The installer is a build-time tool only. Once the bundle is baked into the image, it is not required at run time and does not need to ship in the runtime layer.
Useful commands:
pnpm typecheck
pnpm test
pnpm buildAGENTS.md is the maintainer and agent-facing document for repository rules, internal structure, and implementation invariants.