Skip to content
On this page

Install a source ​

What add does with each of its flags, from the everyday command to a scripted install that resolves a name collision. The CLI reference has the one-row summary of every flag; this page has the behavior behind the add and remove rows. What a source must contain is on the memory files page.

bash
npx -y @vivswan/maxims add @Octocat/rules -g --rule --add-hook

The quickstart shows what that command prints and writes. Every flag below changes one part of it.

Where it lands ​

flagdestinationnotes
-g, --globalthe user scope: every project on the machineon a harness with no global target, warns and skips that harness
-p, --projectthe project scope: the git checkout you are inthe explicit opposite of -g
neithera GitHub source: the project when inside one, else the user scope, as in skillsa local directory source defaults to the user scope (below)
-o, --out <dir>an output folder for a rule file no harness ownsneither scope; a relative path resolves against the cwd, not the project root
--sharewith -p: the source also enters the project lock for teammateswithout it a project install stays yours; refused with -g or -o

Two of -g, -p, and -o together is exit 1, "two destinations given". A team folder inside a repo is the case -o exists for. A source already installed at one scope is not moved by a re-add at another: that is exit 1 naming where it is, and remove followed by add is the way to move it.

A local directory source defaults to the user scope so personal text stays out of the repo; the security page owns what can leak.

What gets installed ​

flagselection
noneevery memory in the source (*)
-m, --memory <names>a comma list, repeatable; * means all
--allevery memory, every harness, no prompt
-l, --lista read-only preview of the source; nothing installs
  • A --memory name the source lacks is exit 3 with nothing written.
  • --all is shorthand for --memory '*' --agent '*' -y. On remove it means every installed source, with -y spelled out, so it is the one remove that needs no separate -y.
  • --list prints the "Found N memories" line and every memory name, one per line, then "Run without --list to install". It writes nothing, never touches state, and ignores --rule, --add-hook, -o, and -y with a warning. Once a source is installed, maxims show <memory> prints one memory in full; check owns it.
  • --list never folds and prints no descriptions. The whole list of names is the point, in a terminal too. The install plan is the one that shows descriptions and folds in a terminal, to one entry plus ... N more; --verbose shows the rest.

Re-running add with a different --memory list replaces the recorded one, shown in the plan first. It never unions.

The output of add @Octocat/rules --list, captured from the built CLI:

text
|
o  Source: https://github.com/Octocat/rules.git
o  Repository cloned
o  Found 4 memories
|
o  Available Memories
|    commit-small-and-often
|    never-retry-without-a-cap
|    prefer-timeouts-to-hangs
|    tests-before-the-fix
|
o  Run without --list to install

Two separate choices ​

--rule and --add-hook record two different intents, and neither implies the other.

flagrecordsevery later sync
--rulethis source's one-liners go to the rule fileregenerates the rule lines; remove always removes them
--add-hookthe harness's sync hook (and MCP server, where declared) is wanted at this install's scopekeeps the hook and the server registered there

The hook carries no source and no filter, so the tenth source adds nothing to it. An install from . registers no hook, so an unpushed edit is never clobbered by a refresh.

The first source from an owner ​

The first time a GitHub or git source is added from an owner (the account on GitHub, the host and first path segment elsewhere) no installed source shares, add prints where it comes from above the plan, once, whether or not it will prompt. --json carries the same facts as provenance, null when the owner is already installed.

text
o  First source from github.com/acme
   https://github.com/acme/rules.git
   commit 1a2b3c4, not pinned (tracks HEAD)
   4 memories

Which harnesses ​

-a, --agent <ids> is a comma list, * for all, with ids from the harness matrix. Without it, the detected harnesses are used. To change one harness of an installed source without a refetch, use link or unlink; the verb notes own how they differ from add -a.

Bodies ​

flagwhere the body livesfor
nonethe canonical home, linked from the destinationevery setup where a symlink works
--copya copy at the destinationsymlink-hostile setups, as skills --copy
--linkthe store entry is a symlink to the source directory, so edits are livelocal sources only; on by default for .

--link is local-only because a fetched source has no representation for it in state.

Refs ​

--pin <sha or tag> tracks that ref instead of the default branch, for GitHub and git sources. It is not offered for a local directory. A GitHub URL with a /tree/<ref> segment sets the same pin, as the source forms show.

The ref is part of the source's key, so an installed source is repinned in two steps: remove it, then add it with the new --pin (or without one, to track HEAD again). A re-add with another ref collides with the installed copy on every shared name and is refused with the installed key and that order named.

Two pins of one repository install side by side when their installed names are disjoint; -m and --rename are the ways to keep them apart.

Path scoping ​

--paths <glob> writes the rule file in the target harness's own path-scoping syntax and is stored per source. A harness with no scoping mechanism warns and skips it.

A scoped rule loads only when matching files are touched, so it trades away "every session". It is opt-in and never a default. A harness may read such a file under a smaller limit than an always-on one (Claude Code: 40,000 characters against 4 MiB), and the writer holds a scoped install to it; the matrix shows it after the always-on budget.

Name collisions and renames ​

A memory's name is its identity. When an incoming memory's name is already owned by another installed source, add stops instead of skipping it, because a silently skipped rule is a rule the user believes is loaded.

text
index:      commit-small-and-often  -> owned by @Octocat/rules
incoming:   commit-small-and-often  from @example-user/dotfiles      COLLISION
interactive:      rename to commit-small-and-often-dotfiles; recorded in state
non-interactive:  exit 6, nothing written, unless --rename names the pair
result:           two rule lines, two names, one rename entry applied by every later sync

The last two lines of the output when the prompt cannot be shown, captured from the built CLI with @Octocat/rules already owning the name; above them are the ordinary progress lines of any add:

text
 ERROR  commit-small-and-often is owned by @Octocat/rules
Tip: --rename commit-small-and-often=<new>

--rename commit-small-and-often=commit-small-and-often-dotfiles records the same entry the prompt would, so a scripted add never meets exit 6 for a collision it knows about. It repeats, one pair per colliding memory.

The rename is a local identity only. The stored file keeps its upstream name and body, the mapping lives in state, and an upstream rename of that file retires the mapping with it.

Non-interactive behavior ​

Detection mirrors skills, which prints "Agent detected - installing non-interactively" when run from inside an agent.

stdout is a TTYagent env detected--yesbehavior
yesnonofull prompts: confirm the plan, confirm hook registration
yesnoyesno prompts, full output
yesyeseitherno prompts, full output, "Agent detected" banner
noeithereitherno prompts, plain output, no spinner, no color; --yes implied on add and sync
anyanyany, with --quietonly the lines a session must hear, or nothing; prompts follow the rules below

Without a prompt, add and sync proceed as if --yes were given, and no other verb does. remove without an explicit -y aborts with exit 1, and --all spells -y out, so it is the one form that passes. The quiet section shows the lines --quiet prints.

disable, enable, link, and unlink take no -y, since they never prompt. A collision without --rename is exit 6 with nothing written.

The output of a non-interactive remove without -y, captured from the built CLI with stdout piped:

text
|
o  Memories to remove: @Octocat/rules
 ERROR  Interactive prompt required but stdin is not a TTY. Nothing was removed. Use -y to run non-interactively.
Built from main at 5f6e985Source: docs/install.md