Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

agent-installer

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/ or commands/

The CLI scans that repository, shows what is available, and installs the selected items into the expected local directories.

What It Supports

  • Codex skills
  • Claude Code skills
  • Claude Code command-style prompts

Current scope:

  • local directories or HTTPS Git repositories
  • convention-based scanning
  • no manifest file

Expected Repository Layout

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 below skills/
  • 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.md and commands/foo.md cannot both exist

Cross-Runtime Invocation Policy

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

# Review

Behavior:

  • the translation is automatic in every install mode: interactive, scan, install --all, and remote Git sources
  • only a literal boolean true translates; "true", 1, yes, false, and a missing setting change nothing
  • the agents/openai.yaml it 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

Skills that already ship Codex metadata

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

Rules:

  • authored interface, dependency, and other metadata is retained, including sibling keys under policy and 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: true wins, 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 scan as well as an install
  • when the Claude setting does not enable the translation, authored metadata is copied verbatim and never validated

Installation

Install directly from GitHub:

pnpm add -g github:lbacik/agent-installer

Or with npm:

npm install -g github:lbacik/agent-installer

For local development, install dependencies:

pnpm install

Build the CLI:

pnpm build

Run it during development:

pnpm dev

Usage

From inside a source repository:

agent-installer

Or point it at a repository explicitly:

agent-installer /path/to/repo

Or point it at an HTTPS Git repository:

agent-installer https://github.com/org/agents.git

Tool Exposure Is Opt-In

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

Commands

Interactive install:

agent-installer [path]

Install interactively from a remote ref:

agent-installer https://github.com/org/agents.git --ref main

Limit the visible interactive list length:

agent-installer [path] --list-length 12

Show scan results only:

agent-installer scan [path]

Print scan results as JSON instead:

agent-installer scan [path] --json

Show scan results for a remote tag, branch, or commit:

agent-installer scan https://github.com/org/agents.git --ref v1.2.0

Options 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 5

Install or update everything found:

agent-installer install [path] --all

By 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-conflicts

Install 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-message

Selection 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 --all

Report the result as JSON instead of the human-readable summary:

agent-installer install [path] --all --json

Remove managed entries by id:

agent-installer uninstall skill:review prompt:commit-message

Interactively list installed managed entries. Unselect entries to uninstall them:

agent-installer list

Limit the visible interactive list length:

agent-installer list --list-length 12

Print managed entries as JSON instead of opening the interactive selection UI:

agent-installer list --json

Create ~/.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 --force

When 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-run

Pass --allow-conflicts to sync the eligible pairs and skip conflicting ones instead of aborting; add --json for a machine-readable report.

Machine-Readable JSON Output

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 --only selector that matches nothing) still exits non-zero, and still prints a JSON object on stdout describing the refusal via an error string.
  • list --json prints 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-conflicts was passed.
  • refused: conflicting artifacts that caused the whole run to abort (no --allow-conflicts); installed and updated are empty in that case, and the object carries an error string. A non-conflict abort, such as an unmatched --only selector, reports the same empty arrays and error string with refused left empty, since no discovered artifact is implicated.
  • pruned: source-missing artifacts removed because --prune was 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", "...": "..." }
  ]
}

Typical Workflow

  1. Change into a repository that contains skills/, prompts/, or commands/.
  2. Run agent-installer.
  3. Review the discovered items and their statuses.
  4. 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.

Build-Time Installation

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/agent

The 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.node requirement (>=20)
  • git
  • network access to the source repository
  • any HTTPS credentials the source repository needs (the installer uses the system git command 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.

Development

Useful commands:

pnpm typecheck
pnpm test
pnpm build

AGENTS.md is the maintainer and agent-facing document for repository rules, internal structure, and implementation invariants.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages