Commands

Every ipm command and flag, in the order of the workflow: onboard → marketplace → catalog → search → pack → install. ipm help <command> prints the same from the CLI.

  1. Getting started
    1. ipm onboard
  2. Marketplace
    1. ipm marketplace
  3. Catalog
    1. ipm catalog
    2. ipm catalog sync
    3. ipm catalog rebuild
    4. ipm catalog serve
    5. ipm catalog scan
  4. Search
    1. ipm search
    2. ipm show
    3. ipm show tree
    4. ipm show graph
  5. Packs
    1. ipm pack
    2. ipm pack build
    3. ipm install
      1. ipm install claude
      2. ipm install gascity
      3. Replay recorded installs
  6. Tools
    1. ipm tui
    2. ipm doctor
    3. ipm uninstall
    4. ipm version, ipm help
    5. ipm completion
  7. Renamed commands
  8. Exit codes
  9. Environment

Flags may come before or after positional arguments. Long flags take two dashes (--update); -g is short for --global. ipm <command> --help and ipm help <command> print a command’s usage and flags, and a mistyped command gets a suggestion.

Getting started

ipm onboard

ipm onboard [--list <path|url>] [--lists <name>]… [--keep|--fresh] [--yes]

Takes you from nothing to a searchable library. It first prints the four concepts, one line each (marketplace, catalog, pack, install), then picks sources from the marketplace’s curated list, adds them, and runs ipm catalog sync, ipm marketplace stats and ipm catalog serve with ipm catalog rebuild, printing ✓ or ✗ for each step. Stats are skipped when gh is missing or not logged in; search is skipped when Docker isn’t running (start Docker, then ipm catalog serve && ipm catalog rebuild). A failed step prints rerun: <command> and exits 1. It ends with ✓ library ready — N sources, M artifacts and try: ipm search … · ipm tui.

If ipm already has sources or a catalog, it prints, for example, found existing ipm data: 26 sources · 3,280 artifacts · synced 2026-10-09 and asks [u]se it · [b]ack it up and start fresh · [q]uit. Backing up renames the home and cache folders to <folder>.backup-YYYYMMDD-HHMM and prints how to undo it: to restore: rm -rf <home> <cache> && mv <home>.backup-… <home> && mv <cache>.backup-… <cache> && ipm catalog serve. If you then quit the checklist, it says no sources added; your data was backed up to … and prints the same line. With sources you already follow, enter with nothing new ticked continues with those (0 new repos — y continue with the N you follow), so rerunning ipm onboard after a failed step resumes it. While filtering, space on a list ticks only the repos shown.

The curated list is fetched each time and cached under the cache folder’s curated/; offline, the cached copy is used. In a terminal it shows a checklist of the lists, which expand into their repos; see Getting started for the keys. The line above the key hints shows the highlighted repo’s topics and homepage. On a fresh start “AI · Agentic Engineering Methods” and “AI · Skills, Plugins & Registries” are ticked; repos you already follow show ✓ and nothing is ticked. Rerunning ipm onboard adds only what is new.

Flag Does
--list L The curated list: a URL or a file; a GitHub blob/ link is read as the raw file. Default https://raw.githubusercontent.com/thinkjones/intelligent-package-manager/main/source-packs.toml. The format is in Curated lists.
--lists NAME Follow every repo in this list, by exact name (repeatable). Skips the checklist.
--yes Follow the pre-ticked lists without showing the checklist.
--keep Use existing ipm data without asking.
--fresh Back up existing ipm data and start fresh without asking.

When not in a terminal, --lists or --yes is required, and so is --keep or --fresh if data exists. --keep and --fresh together are a usage error.

It then runs five steps: add the sources, ipm catalog sync, ipm marketplace stats, ipm catalog serve and ipm catalog rebuild’s load. In a terminal, one line is redrawn while they run:

[████████░░░░░░░░░░░░] 3/5 stats · 17/42 anthropics/skills · 0:48

The bar covers all five steps and moves inside stats (per repo) and load (per 500 documents); after the step comes the last line it printed or its count, then the time since the steps began. The steps’ own output is not shown. Each finished step leaves one line, such as ✓ sync · catalog: 3280 record(s), 2 warning(s); run ipm catalog rebuild to read the warnings. If a step fails, everything it printed is shown, then ✗ <step> and rerun: <command>. When stdout is not a terminal there is no bar and each step prints what it prints on its own.

Marketplace

The marketplace is the curated list of repos worth following (by default this repo’s source-packs.toml). Following a repo makes it a source: ipm fetches, pins and catalogs it.

ipm marketplace

ipm marketplace [--list <path|url>] [--lists <name>]… [--followed]
ipm marketplace add <repo>…
ipm marketplace remove <repo>…
ipm marketplace import <path/to/manifest.toml>
ipm marketplace stats

Run alone, it shows the curated repos grouped by list, each with a ✓ when you follow it, its stars and its description, then the repos you follow that no list holds under Other, and ends with ✓ follows N of the repos shown. The curated list is fetched each time and cached under the cache folder’s curated/; offline, the cached copy is used.

Flag Does
--list L The curated list: a URL or a file. Default https://raw.githubusercontent.com/thinkjones/intelligent-package-manager/main/source-packs.toml.
--lists NAME Show only this list, by exact name (repeatable).
--followed Show only the repos you follow, grouped by list, each with its alias, repo, stars, ref and pinned commit (not synced until ipm catalog sync), then its description, topics and homepage. See List and remove.
Subcommand Does
add Follow repos: owner/repo[@ref], a git URL (https://, ssh://, file://, git@) with an optional #ref, or an external plugin ID (plugin:<marketplace repo>#<name>), which follows the repo the plugin lives in. A repo already followed, or two repos that would share an alias, is refused and nothing is written.
remove Stop following repos. An unknown repo is an error and nothing is written.
import One-time migration from an aihs-era manifest.toml.
stats Fetch GitHub stars, forks, topics, licence, description and last push for every repo in the catalog into github.jsonl. Needs gh, logged in.

add, remove and import only edit the source list; run ipm catalog sync after.

Catalog

ipm catalog

ipm catalog
ipm catalog sync [--upgrade]
ipm catalog rebuild
ipm catalog serve [--stop]
ipm catalog scan [--paths] [owner/repo…]
ipm catalog search [flags] <query>

Run alone, it prints a summary: how many sources you follow and how many are synced, the items by type, when the catalog was last built, and the search server’s state (running, N document(s), or how to set it up).

sources     0 followed, 0 synced
items       none
last index  never — run `ipm catalog sync`
search      no search server set up — run `ipm catalog serve`

ipm catalog sync

ipm catalog sync [--upgrade]

Has pnpm fetch and pin every source, then rebuilds the catalog and, when the search server is up, reloads the search index. With the server off it prints - search not loaded: run ipm catalog serve then ipm catalog rebuild. If pnpm fails, the catalog is unchanged and ipm exits 1.

Flag Does
--upgrade Move every source to the latest commit of its branch or tag first.

ipm catalog rebuild

ipm catalog rebuild

Rebuilds the catalog from your synced sources, without fetching, then reloads the search index when the server is up (the same hint otherwise). Use it after ipm marketplace stats to refresh the stars in search.

ipm catalog serve

ipm catalog serve [--stop]

Starts Typesense in Docker as container ipm-typesense on 127.0.0.1:8108, with its data in your cache folder.

Flag Does
--stop Stop the server. Its data is kept.

ipm catalog scan

ipm catalog scan [--paths] [owner/repo…]

Reports each synced source’s commit and its plugins, Copilot plugins, marketplaces, and skills, hooks and agents folders.

Flag Does
--paths List every folder found, not only the counts.
ipm search [flags] <query>
ipm catalog search [flags] <query>

Hybrid keyword and semantic search; the two spellings are the same command. Needs ipm catalog serve and a loaded index (ipm catalog sync and ipm catalog rebuild load it). Each hit’s full ID is printed on the line under it. The table fits the terminal width (or COLUMNS), dropping DESCRIPTION, then PLUGINS, then REPO when narrow; piped output isn’t cut.

Flag Does
--type T Only this type (repeatable): repo, marketplace, plugin, copilot-plugin, skill, agent, command, hook, mcp.
--repo R Only artifacts from this repo (repeatable).
--tag T Only artifacts with this tag or topic (repeatable).
--limit N Results to show, at most 250. Default 20.
--page N Page of results, --limit per page.
--all Every result, fetched 250 at a time.
--keyword Keyword ranking only.
--explain Add KW and SEM columns.
--ids Only each hit’s ID, one per line. Doesn’t combine with --json.
--json One JSON line per hit.

ipm show

ipm show [--json] <id>
ipm show tree [flags] <id>
ipm show graph [flags] <id>

An artifact’s fields, its repo’s GitHub stats, and its neighbours: up: (what contains it, within its repo), down: (what it contains) and used by: (external entries in other repos that reference it — for a repo, anything in it). Reads the catalog; no search server needed.

Flag Does
--json Print the catalog record as JSON.

ipm show tree

ipm show tree [flags] <id>

An artifact’s relationships as a tree. Reads the catalog.

Flag Does
--up Walk to what contains the artifact, instead of what it contains.
--depth N Levels to show. Default: 3 from a repo, otherwise all.
--all Show every child, not the first 25.

ipm show graph

ipm show graph [flags] <id>

An artifact’s neighbourhood as a Mermaid or Graphviz graph. Reads the catalog.

Flag Does
--format F mermaid (default) or dot.
--depth N Levels to include. Default 2.
--up Walk to what contains the artifact.
--all Include every child, not the first 25.

Packs

ipm pack

ipm pack new <name> [--description …] [-g]
ipm pack add <pack> <id>… [-g]
ipm pack remove <pack> <id>… [-g]
ipm pack list [-g]
ipm pack show <pack> [-g]
ipm pack build [-g] [--update] [--no-validate]
ipm pack install claude|gascity [pack] …      (same as ipm install claude|gascity)
Subcommand Does
new Create a pack. Creates ipm.toml in the current folder if no folder above has one. The name must be kebab-case (lowercase letters, digits and single hyphens, as claude plugin validate --strict wants) and not one Claude Code reserves; otherwise it is a usage error (exit 2) that suggests the kebab form, e.g. my-pack for my.pack.
add Add catalog items. Every ID is checked first; if any is unknown or can’t be picked, nothing is added. Duplicates are ignored.
remove Remove items.
list Every pack in the scope: name, item count, description.
show A pack’s items: type, name, repo, stars and ID, then one installed: line for each place the pack is installed.
build Build the scope’s packs into plugins; see ipm pack build.
install The same as ipm install.
Flag Does
--description D The pack’s description (new). Default: ipm pack <name>, since claude plugin validate --strict refuses a plugin without one.
-g Use your user scope ($IPM_HOME/global) instead of the project.

ipm pack build

ipm pack build [-g] [--update] [--no-validate]

Builds every pack in the scope into a Claude Code plugin under .ipm/plugins/, makes sure the repos of any whole plugins you picked are fetched (so ipm install gascity can read their parts), writes .ipm/.claude-plugin/marketplace.json and .ipm/ipm.lock (which records each whole plugin’s parts), and validates the result with claude plugin validate --strict. Last, it writes .ipm/build.stamp, a hash of the packs, the lock and the ipm version, which lets ipm install skip a build that is already current. A failure leaves the previous build and lock in place. It only builds: it never touches your Claude Code configuration. A pack with no description gets ipm pack <name> in its plugin and marketplace entry.

Repos come from your home, not the project. A repo whose synced source ($IPM_HOME/sources) is at the pinned commit is read from there, with no pnpm and no network, so a project whose pins match your sources builds straight away. Any other pin is fetched into the pin store, $IPM_HOME/pinned, shared by every project: the build prints the pins it fetches and the pnpm command (pnpm install … --store-dir $IPM_HOME/store --dir $IPM_HOME/pinned), and a pin already there is never fetched again. If pnpm fails the build prints its output and ✗ pnpm failed, exits 1 and changes nothing in .ipm/. Builds in several projects at once take turns on the pin store. .ipm/ holds no pnpm files: the build removes package.json, pnpm-lock.yaml, .pnpmfile.cjs and node_modules/ left there by older versions of ipm. The pin store is never pruned; ipm uninstall --purge deletes it with the rest of your home.

Flag Does
-g Build your user scope’s packs.
--update Re-pin every item to the commit in your current catalog, instead of keeping the pins in .ipm/ipm.lock.
--no-validate Skip claude plugin validate --strict.

ipm install

ipm install claude [pack] [--scope user|project|local] [--path DIR] [-g] [--update] [--dry-run]
ipm install gascity [pack] [-C DIR] [--name NAME] [-g] [--force] [--update] [--dry-run]
ipm install [-g]

ipm install claude

ipm install claude installs pack into Claude Code. pack may be left out when the scope has only one. It builds first when .ipm/ is out of date (the build stamp no longer matches), printing build is current when it is not needed, then prints each claude command before running it and needs no confirmation: typing the command is the decision. Afterwards it records the install: in the pack’s [[packs.<name>.installs]] table in ipm.toml for a project pack (a path inside the project is stored relative to it), or in $IPM_HOME/installs.json for a user-scope pack. Installing the same pack to the same target, path and scope again replaces its record; project and local scope at one folder are two records. ipm pack install claude is the same command.

Flag Does
-s, --scope S user (every project, just you), project (a folder, shared through its .claude/settings.json) or local (a folder, just you). Default project; user with -g.
-p, --path DIR The folder for project or local scope. Default: the pack’s project. ~ and relative paths work.
-g, --global Install a pack from your user scope.
--update Re-pin every item to your current catalog and rebuild first.
--dry-run Print the claude commands without building, running or recording anything. Can’t be combined with --update (usage error, exit 2: install claude: --dry-run and --update can't be used together).

The commands it runs: claude plugin marketplace add <project>/.ipm --scope S (always — Claude Code accepts a repeat), claude plugin marketplace update <name> when Claude Code already knows the marketplace, then claude plugin install <pack>@<name> --scope S, or claude plugin update … when the pack is already installed there. Needs claude on your PATH.

ipm install gascity

ipm install gascity writes pack as a Gas City pack folder, <DIR>/<NAME>, that a city can import. Without -C, DIR is ./.ipm/.gascity in the project you run it in (the nearest folder holding an ipm.toml, else the working directory), with -g too: your user-scope packs are written into the project, from the user scope’s build. It is created when missing (and added to .ipm/.gitignore if that file lacks it); ipm pack build leaves it alone and git-ignores it. Like install claude it builds first unless the build is current. It reads no Gas City config and edits no city: it writes the folder, records the install and prints how to import it. ipm pack install gascity is the same command.

Without pack it installs every pack of the scope (with -g, every user-scope pack), in ipm.toml order, each into <DIR>/<its name>. It checks -C once, before building, so a bad DIR fails with one error and no build. It builds once first, then prints each pack’s output and ✓ installed <pack> (gascity <folder>), or ✗ <pack>: <error> for a pack that fails, which doesn’t stop the rest. A pack whose name has a ., which a Gas City folder name can’t, fails with pack name "<pack>" isn't a valid Gas City folder name — install it alone with --name. With more than one pack it ends with ✓ installed N packs or ✗ N of M packs failed; any failure exits 1. With no packs it fails with no packs in <path>/ipm.toml — create one with `ipm pack new <name>`.

Flag Does
-C, --dir DIR The folder to write the pack folder into; it must exist. Default: ./.ipm/.gascity in the project, with -g too (ipm creates it; a dry run doesn’t). ~ and relative paths work.
--name NAME The pack folder’s name. Default: the pack’s name. It must start with a letter or digit and use only letters, digits, _ and -, else a usage error (exit 2). It names one folder, so it needs a pack: without one it is a usage error, install gascity: --name needs a pack — it names one pack folder.
-g, --global Install packs from your user scope, into the project’s .ipm/.gascity. The installs are recorded in $IPM_HOME/installs.json with the absolute folder, so ipm install -g replays them into that project from anywhere.
--force Replace a folder ipm did not write, or take over another pack’s install folder, removing that pack’s record (for every pack, without pack).
--update Re-pin every item to your current catalog and rebuild first.
--dry-run List the files that would be written (for every pack, without pack), without building, writing or recording anything in the project (a repo missing from your home is still fetched into $IPM_HOME/pinned, to read it). Needs a current build: --dry-run needs a current build — run ipm pack build first. If the folder would be refused, it prints would refuse: <message> and exits 1. Can’t be combined with --update (usage error, exit 2: install gascity: --dry-run and --update can't be used together).

The folder holds pack.toml, skills/<name>/ (each skill folder copied whole), agents/<name>/prompt.md with agents/<name>/agent.toml, and mcp/<name>.toml. Files are copied, not hard-linked, and two runs give byte-identical folders. Names are lowercased. For skills and agents _ is kept and other characters become -; MCP server names also turn _ into - (they use only a-z 0-9 -). Clashes within a kind get -<owner>, then -2, -3. An agent’s prompt is its body without the frontmatter, as plain markdown (Gas City does not template it).

Gas City has no equivalent for some items, so ipm skips them and prints each as a warning:

Message Meaning
skipped <id>: Gas City has no slash commands Commands, from a pack or from a whole plugin, are not written.
skipped <id>: Gas City packs have no hooks A plugin’s hooks are not written.
skipped <id>: external plugin — its files live in another repo; add its skills and agents directly A plugin whose files are in another repo. Add its skills and agents to the pack instead.
skipped <id>: its parts aren't recorded — run ipm pack build --update A whole plugin that the lock has no parts for; ipm pack build --update records them.
skipped <id>: nothing Gas City can hold A whole plugin with no recorded parts (parts = [] in ipm.lock). A plugin whose parts are all commands or hooks gets one skip line per part instead.
agents/<name>: frontmatter is not valid YAML — wrote the body only The agent’s frontmatter doesn’t parse. ipm still writes prompt.md with the body and an agent.toml holding only provider = "claude".
agents/<name>: dropped tools, color — Gas City agents have no equivalent Agent frontmatter keys other than name, description and model are dropped, one warning per agent.
mcp/<name>: dropped <keys> MCP server keys Gas City doesn’t know (Claude’s type is dropped silently).
mcp/<name>: uses ${CLAUDE_PLUGIN_ROOT}, which Gas City does not set The server’s command, arguments, environment or URL use Claude Code’s plugin path, which won’t resolve in a city.

ipm writes into <DIR>/.<NAME>.ipm-tmp and swaps the result in, replacing a folder it wrote earlier completely, so items removed from a pack leave no files. A folder that exists, isn’t empty and wasn’t written by ipm (its pack.toml lacks ipm’s header line) is refused with exit 1: <path> exists and was not written by ipm — use --force to replace it. A folder that a recorded install of another pack wrote, or of the same pack from the other scope (the project’s ipm.toml, $IPM_HOME/installs.json, and the ipm.toml of the project whose .ipm/.gascity holds the folder are checked), is refused the same way: <path> holds pack <other> (project|global) — use --name to pick another folder, or --force to replace it. A user-scope pack and a project pack of the same name clash this way in .ipm/.gascity; installing the same pack from the same scope again is a reinstall. With --force the new install takes the folder over: ipm replaces it, removes the other pack’s record from whichever file holds it, prints replaced <other> (project|global)'s install at <path>, and records the new install, so a replay never fights itself. It changes the records before writing the folder: if the write fails, the records already name the new pack, and a replay finishes the job. A dry run with --force only prints would replace <other> (…)'s install at <path>. The record counts even when its folder is gone. The checks run before any build when you name a pack; without one, the build comes first. Even with --force, ipm refuses a folder that is, or holds, the project (the one you run it in, with -g) or $IPM_HOME: refusing to replace <path>: it holds the project or … it holds ipm's home.

Afterwards it records the install (target = "gascity", path, name), as for Claude Code, and prints:

Import it into a city's pack.toml or city.toml:
  [imports.<name>]
  source = "<folder>"

Replay recorded installs

Bare ipm install replays every install recorded for the scope (-g for your user scope). With none recorded it is a usage error that names the targets. A target other than claude or gascity is a usage error. Before running anything it checks every record’s target: a record of a target this ipm doesn’t know is reported as <pack> → <target> …: unknown target, and nothing is installed. A gascity record whose folder is <root>/.ipm/.gascity, for a <root> folder that exists (a project, a folder you ran ipm install gascity -g in, or $IPM_HOME/global, where ipm wrote user-scope packs before), recreates that folder when it is gone. A record that fails (a stale one whose folder was deleted, say) doesn’t stop the others: its error is printed as ✗ <pack> → <target> <scope or name> <path>: <error>, and the replay ends with exit 1 and ✗ N of M recorded installs failed; if a record is stale, remove it from <file>, where the file is ipm.toml for a project or $IPM_HOME/installs.json with -g, each with its path. There is no command to forget a record: delete it from that file.

Tools

ipm tui

ipm tui [-g]

Four tabs in one screen: Catalog (search every artifact, see its tree, add it to a pack), Packs (the packs over the selected pack’s items grouped by repo: create a pack, build every pack, install one, remove items, open an item in the Catalog), Installs (the recorded installs, reinstall or make a new one) and Marketplace (follow repos from the curated lists and run ipm catalog sync). Installs go through a form that shows the exact commands for ipm install claude or ipm install gascity before running them. Uses the search server when it is running and indexed, and keyword search over the catalog otherwise. Needs a terminal and a catalog. It opens on the tab bar: ← → pick a tab, tab moves into its panes and on through them back to the bar, esc backs out a step at a time to the bar, 1–4 and [ ] switch tabs, / searches or filters, ? lists every key of the tab, q quits. The Catalog’s search box takes repo:owner/repo and pack:name among its words. Global packs and installs, listed from a project with g, are read-only; the TUI doesn’t reload the catalog while it runs, so restart it after a sync. See Use the TUI for each tab’s columns and keys.

Flag Does
-g, --global Use your user scope’s packs and installs ($IPM_HOME/global).

ipm doctor

ipm doctor

Checks the home and cache folders, pnpm, sources, the pin store (pinned: how many commits $IPM_HOME/pinned holds, or none yet), catalog, Docker, the search server and claude. ✓ is fine, ! is a warning, ✗ is a problem; any problem makes it exit 1.

ipm uninstall

ipm uninstall [--purge] [--yes] [--force]

Removes the ipm-typesense container (when Docker is available) and the running ipm binary. Your data is kept, and it says where: ipm onboard will offer to use it or back it up. A development build (a binary in a bin/ folder inside an ipm checkout) is refused without --force.

Flag Does
--purge Also delete ipm’s home and cache. You type the home path to confirm.
--yes With --purge, delete without asking. Required with --purge when not in a terminal.
--force Remove a development build in a git checkout too.

ipm version, ipm help

ipm version              (or ipm --version)
ipm help [command]       (or ipm <command> --help)

ipm version prints the release ipm was installed from, for example ipm 0.2.0. A build of a checkout (./install.sh in a clone, or bin/ipm) prints ipm dev.

ipm completion

ipm completion zsh|bash|fish|powershell

Prints a shell completion script for commands and flags. For zsh: ipm completion zsh > "${fpath[1]}/_ipm", then start a new shell; ipm completion <shell> --help shows the steps for each shell.

Renamed commands

The old command names are removed. Typing one is a usage error (exit 2) that names its replacement.

Old New
ipm source add, remove, import ipm marketplace add, remove, import
ipm source list ipm marketplace (the curated list with ✓), or ipm marketplace --followed (your sources)
ipm stats ipm marketplace stats
ipm sync [--upgrade] ipm catalog sync [--upgrade]
ipm index, ipm load ipm catalog rebuild
ipm serve [--stop] ipm catalog serve [--stop]
ipm scan ipm catalog scan
ipm tree, ipm graph ipm show tree, ipm show graph
ipm install (the build) ipm pack build
ipm deploy ipm install claude

Exit codes

Code Means
0 Success
1 Failure: a bad source, pnpm missing or failed, unsynced sources, an item not found or not pickable, validation failed, a failed onboard step, or a doctor problem
2 A malformed command line

Environment

Variable Does
IPM_HOME Where your sources, the pnpm store and user-scope packs live. Must be absolute.
IPM_CACHE_DIR Where the catalog and search data live. Must be absolute.
COLUMNS The width ipm search fits its table to when stdout isn’t a terminal.
XDG_DATA_HOME, XDG_CACHE_HOME Honoured on every OS when the variables above are unset.

Defaults are in Files and paths.