Extensions¶
Override built-in commands or add new ones from an extension repository or
a local path: a compliance check before deploy, a team command, a different eval
transport. New third-party code needs an explicit trust decision.
Experimental
The extension manifest format and the extension commands may still change in a breaking
way. Pin the CLI version if you depend on them.
What an extension can do¶
An extension is a directory holding a graph-agents-cli-extension.yaml, so any git repository
(or a folder on disk) can serve one. It changes commands; it cannot add a deployment target or
a framework.
| It can | Example |
|---|---|
| Add a command | graph-agents-cli release-notes |
| Override a top-level command | deploy, lint, build |
| Override one subcommand of a group, by its dotted name | eval.generate, secrets.apply, infra.check |
| It cannot | Because |
|---|---|
Override a whole group (eval, scaffold, secrets, infra, ...) |
the other subcommands keep their built-in behaviour; the CLI skips it with a warning |
Override install or extension |
they are how you repair or remove an extension |
| Add a command that already exists | use override |
Two overrides reach further than their name:
eval runhonours both stage overrides. Overridingeval.generateoreval.gradechanges the compositeeval runexactly as it changes the standalone command.createandscaffold createare one command: overridingscaffold.createtakes overcreatetoo.
Whenever extension commands apply, every CLI call says so on one line, so a takeover is always visible:
Warning: graph-agents-cli: applying 2 extension command(s): lint [team-tools/project], release-notes [team-tools/project]
Use an extension¶
# Add an extension, pinned to a tag (recommended)
graph-agents-cli extension add acme/gacli-extensions#soc2 --ref v1.2.0
# What is active, in which scope, with which commands
graph-agents-cli extension list
# Advance the pin within the tracked ref
graph-agents-cli extension update soc2
# Drop it and its vendored copy
graph-agents-cli extension remove soc2
graph-agents-cli info also lists the active extensions, their sources and any conflicts.
extension add REFERENCE accepts:
| Reference | Meaning |
|---|---|
acme/ |
a repository on github.com |
acme/ |
one extension of a repository that holds several |
https://git., git@git. |
any git host (https://, http://, ssh://, scp form) |
../my-extension, /abs/path, ~/ext, local@<path> |
a local directory, for development |
--ref <ref> |
the branch, tag or commit SHA to pin |
A repository is cloned with your ambient git configuration; the CLI neither asks for nor stores
credentials. A bad local path is exit 3; a git or network failure is exit 2. A bare name
(soc2) is the first-party shorthand for an extension in the graph-agents-cli repository; none
are published there yet.
Scopes¶
| Scope | Recorded in | Applies to |
|---|---|---|
| project (default) | graph-agents-cli-extensions., with a working copy vendored under extensions/ |
this repository: commit both, and teammates and CI get the same commands |
user (--global) |
~/.config/ (%APPDATA%\graph-agents-cli on Windows) |
every project on the machine |
When both scopes define a command, the project wins; two extensions claiming one command in one
scope is first-wins, and extension list and info show the conflict. Prefer project scope
unless you want an extension machine-wide. A local path is recorded relative to the project
root (absolute with --global).
Trust¶
Every reference except the first-party shorthand is third-party code that runs on your machine
when its commands are invoked, so add asks first. -y trusts it without asking; use it only
for automation you control. Without a terminal to ask on (CI, a pipe), add never prompts:
Extension source 'local@../team-tools' is third-party. It can run arbitrary code on your machine when its commands are invoked.
Not asking: stdin is not a terminal. Pass -y to trust this extension non-interactively.
Error: Aborted: extension not trusted.
add then exits 1. extension update resolves the tracked ref first: an extension whose code
did not change is "Already up to date" without a prompt, and new code needs your trust again
(without a terminal it keeps the installed copy and exits 1).
Project extensions run with the trust of the repository you are in
A project-scope extension, or an ad-hoc graph-agents-cli-extension.yaml at the project
root, loads automatically for anyone who runs the CLI in that repository. Review changes
to them like code. The generated .github/CODEOWNERS covers
graph-agents-cli-extensions.yaml and extensions/; add the ad-hoc file to it if you
use one.
Pinning and updates¶
extension addresolves the ref to an exact commit and recordssource,refandshaingraph-agents-cli-extensions.yaml.graph-agents-cli installrestores a missing or stale vendored copy from the pinned commit. It never advances a pin.extension update [NAME]advances pins to the latest commit of the tracked ref (every extension withoutNAME). A pinned tag or commit resolves to itself: to move to another tag, runextension addagain with the new--ref.- A failed re-
addleaves nothing installed rather than the previous pin: re-add the old ref to restore it.
Write an extension¶
This example adds a release-notes command and makes lint refuse TODO markers before
running the built-in lint. The directory:
team-tools/
├── graph-agents-cli-extension.yaml
└── scripts/
├── lint.sh
└── release_notes.py
# yaml-language-server: $schema=https://raw.githubusercontent.com/ss7172/graph-agents-cli/main/schemas/graph-agents-cli-extension-v1alpha1.schema.json
schema: graph-agents-cli-extension/v1alpha1
name: team-tools
description: A release-notes command and a stricter lint.
requires:
agents_cli: ">=0.2,<0.3"
on_incompatible: warn
commands:
add:
release-notes:
run: ["python3", "scripts/release_notes.py"]
description: Print the release notes for a version.
override:
lint:
run: ["bash", "scripts/lint.sh"]
description: Refuse TODO markers in app/, then run the built-in lint.
import sys
version = sys.argv[1] if len(sys.argv) > 1 else "unreleased"
print(f"Release notes for {version}")
#!/usr/bin/env bash
set -euo pipefail
if grep -rn "TODO" app/ --include='*.py'; then
echo "team-tools: resolve the TODO markers above first" >&2
exit 1
fi
exec graph-agents-cli lint "$@" # overrides are off here: this is the built-in lint
Add it to a project and use it:
graph-agents-cli extension add ../team-tools -y
graph-agents-cli release-notes v1.4.0
graph-agents-cli lint
Added extension 'team-tools' (project scope) from local@../team-tools.
Extensions are experimental; the manifest format may still change.
Warning: graph-agents-cli: applying 2 extension command(s): lint [team-tools/project], release-notes [team-tools/project]
Release notes for v1.4.0
--help lists both commands, each marked [↑ team-tools]. To share the extension, move the
directory into its own git repository, tag it, and others run graph-agents-cli extension add
<org>/<repo>#team-tools --ref v1.0.0: the file does not change.
For one extension that lives in the project itself, skip extension add: a
graph-agents-cli-extension.yaml at the project root (next to the manifest) is loaded at project
scope automatically. Only one, at that exact path.
How commands run¶
run:is a command vector, run with no shell. The user's arguments are appended verbatim. Start it with a program (python3,bash,uv run ...), not a bare script path, which relies on a shebang and never runs on Windows.- Paths resolve against the extension. A
run:token written as a path (scripts/lint.sh) that exists in the extension directory is made absolute, so the script is found from any working directory.$GRAPH_AGENTS_CLI_EXTENSION_DIRalso names that directory, for sibling files. - The command runs in the project root and exits with the child's exit code.
- Calling the built-in is safe. An extension command runs with
GRAPH_AGENTS_CLI_DISABLE_OVERRIDES=1, sograph-agents-cli lintinside the wrapper hits the built-in, with no recursion. Chain steps in a wrapper script, sincerun:is one vector.
The manifest¶
| Key | Required | Meaning |
|---|---|---|
schema |
no | graph-agents-cli-extension/: the manifest format, not the CLI version |
name |
no | the extension's name (defaults to its directory or repository name) |
description |
no | what the extension does |
requires. |
no, but always set it | the CLI versions it supports, for example ">=0. |
requires. |
no | warn (default: install, run, warn when out of range) or error (add and update refuse; if a CLI upgrade leaves the range, its commands fail with the range and the fix instead of silently running the built-in) |
commands. |
a new command: run (a non-empty list) and an optional description |
|
commands. |
replace a built-in: lint, or eval.generate for a subcommand |
Unknown keys are refused, so a typo fails loudly. The machine-readable schema is
schemas/graph-agents-cli-extension-v1alpha1.schema.json in the graph-agents-cli repository;
point a yaml-language-server modeline at it, as above, for validation in your editor.
Set the lower bound of requires.agents_cli to the major.minor that graph-agents-cli
--version prints, and the upper bound to the next minor while the CLI is 0.x (a 0.x minor
release may break compatibility), the next major from 1.0 on.
In CI and CD¶
GRAPH_AGENTS_CLI_DISABLE_OVERRIDES=1 makes the CLI ignore every extension override. Every
generated CI and CD job sets it, so the pr_checks gate always runs the CLI's own lint and
eval run (CI/CD). Set it yourself to run a built-in once, for example when an
override is broken:
Environment variables lists the CLI's other variables.
Next steps¶
-
Every subcommand and flag.
-
The generated workflows, which run with overrides disabled.
-
GRAPH_AGENTS_CLI_DISABLE_OVERRIDESand the CLI's other settings.