Skip to main content

aicp

AI commit + push, with a git-verified result summary.

aicp runs an AI coding CLI to write your commit message(s) and push, walking a fallback chain until one exits 0; anything not installed is skipped. It sends that CLI two literal prompts, /commit then /safe-git-push.

The chain is yours to arrange, and it isn't a fixed set. The built-in registry ships six CLIs — copilot, agy, codex, claude, vibe, grok — but that's only the default order, not a hardcoded pipeline. Move any one of them to the front with aicp --swap-ai, set the whole order from aicp --config or AICP_CLI_ORDER, and add your own CLIs (or drop built-in ones) with aicp --agents. Every agent in the resolved registry is a first-class link in the chain — there is no privileged first CLI and no cap on how many you run with.

The part that matters: aicp never trusts the CLI's own account of what happened. An AI CLI can print "pushed!" and exit 0 while /safe-git-push quietly aborted inside it. So every number in the result summary — new commits, ahead/behind, whether the branch is actually in sync — is read back from git itself after the CLI is done, never taken from its output. A failed git fetch is never read as "already in sync" either: a stale remote-tracking ref resolves just fine and would otherwise report a clean push that never reached the remote.

Contents

Getting started

Using it

Reference

Requirements

  • Python 3.10 or newer
  • uv — the install path below
  • git
  • At least one AI CLI from the chain on PATH (a built-in one, or your own via Custom agents)

The Python package itself has no runtime dependencies.

Install

uv tool install aicp-cli             # latest release
uv tool install aicp-cli==X.Y.Z      # pin to a specific version

The distribution is aicp-cli; the command it installs is aicp. (The plain aicp name on PyPI belongs to an unrelated 2021 project — don't install it.) Replace X.Y.Z with the release version you want; re-running either command switches an existing install to that version.

uv tool upgrade aicp-cli
uv tool uninstall aicp-cli

Set up skills

aicp --config   # -> Skills

/safe-git-push only means something if a skill by that exact name exists in the CLI's own config directory — otherwise the CLI receives a slash command it has never heard of and improvises. aicp ships and installs that skill for you, but it never overwrites a skill you already have: a file with no aicp version marker next to it is treated as yours and left alone. /commit is always left to your existing definition.

Targets follow each CLI's own config directory, not its binary name — agy (this project's name for the Gemini CLI) reads ~/.gemini, not ~/.agy:

CLI Config dir /commit installed? /safe-git-push installed?
claude ~/.claude No — keeps your existing commands/commit.md Yes
codex ~/.codex Yes Yes
copilot ~/.copilot Yes Yes
agy (Gemini CLI) ~/.gemini Yes Yes
vibe ~/.vibe Yes Yes
grok $GROK_HOME when set, otherwise ~/.grok Yes Yes

Every configured CLI gets /safe-git-push. /commit is installed everywhere except claude, which already resolves /commit from commands/commit.md — a second definition under skills/ would compete with it. Which skills an agent gets is data, not a branch: it's the skills field of that agent's registry entry, so a custom agent declares its own list the same way.

The vendored text is rewritten per CLI on install — the shipped copy talks about .claude/ and CLAUDE.md, which mean nothing to codex or agy, so each install substitutes that agent's own config_dir name and memory_file.

A CLI whose config directory doesn't exist at all is skipped, never created — aicp only ever installs into a CLI you've actually set up.

Run it

The whole command surface is two things to remember:

aicp             # commit, then push
aicp --config    # settings, skills, health check — everything else lives here

A plain aicp run: checks whether anything is pending (skipping the AI CLI entirely on a clean, already-in-sync repo), scans for secrets, runs /commit through the fallback chain, then /safe-git-push the same way, then prints the git-verified result table described above. -v/--verbose streams each CLI's raw output live instead of showing a spinner.

Every long flag also works without its leading -- — aicp config is the same as aicp --config.

aicp run: commit, push, and the git-verified result table

aicp --config is one menu for everything that isn't "commit and push right now": which steps run, message language, fallback CLI order, the skills install/upgrade view, and a health check.

Fallback order

The order is a saved preference, not a property of the tool. Three ways to set it, all writing the same aicp_cli_order key:

aicp --swap-ai   # pick a CLI, it trades places with whoever holds #1
aicp --config    # the AI CLI order row, arrow keys
# ~/.aicp/config.json — or AICP_CLI_ORDER="claude codex" aicp for one run
{ "aicp_cli_order": "claude codex copilot agy vibe grok" }

--swap-ai is a rotation, not a destructive set: the CLI you pick moves to #1 and the old #1 takes its place, so nothing falls out of the chain. It lists every agent in the registry, including ones whose binary isn't on PATH — a missing binary must not hide a choice you're about to install. Naming only the first few is enough; everything you leave out is appended behind them in registry order.

Fallback results and quota limits

The opening run panel prints the resolved chain, and the commit panel and final RESULT table name the CLI that handled each step (— when skipped). When a CLI emits a supported, exact quota/rate-limit signal, aicp records the outcome as quota, notifies through the usual notification path, and excludes that CLI from the rest of that one commit/push flow. The exclusion also persists: the CLI stays skipped for AICP_QUOTA_COOLDOWN seconds (default one hour, state in ~/.aicp/quota.json), because a token or rate-limit wall normally stands for hours and every run inside that window would otherwise burn a full budget per step on a CLI that cannot succeed. A skipped CLI prints how long is left; AICP_QUOTA_COOLDOWN=0 switches the cooldown off entirely, and deleting the file clears it.

Only the quota outcome starts a cooldown. A timeout does not — a CLI that hangs on its rate limit instead of exiting is indistinguishable from one merely running long, and sidelining it for an hour on that guess costs more than the retry does.

Exact detection is intentionally narrow. It is supported for Codex, Claude, Vibe, and Grok only; Copilot, agy and any agent you add yourself have no verified quota signature, so a nonzero exit from those remains an ordinary failure and is still eligible for the next step.

--undo

aicp --undo

Runs git reset --soft HEAD^ on the last commit — the escape hatch for a bad commit message or a wrong stage. Changes land back in the index, not lost, not pushed. It never calls an AI CLI and refuses outright (HEAD untouched) in four cases, because once a commit reaches the remote other clones or CI may already be building on it:

  • there's no branch to compare against (detached HEAD),
  • there's no parent commit to reset onto,
  • the last commit is already on the remote,
  • the remote can't be verified at all (deleted remote, failed fetch, never pushed) — "can't verify" is treated as the risky case, never as "safe."

Automation / CI

Everything below is a non-interactive escape hatch for scripting; day-to-day use is the two commands above.

aicp --doctor --json          # skill-install status for every configured CLI, as JSON
aicp --install-skills --yes   # install what's missing, upgrade what's outdated
aicp --install-skills --force # also replace files aicp doesn't recognize
aicp --agents                 # the agent registry; see Custom agents for its verbs

--doctor --json reports, per CLI and per skill: the target path and whether it's missing, current, an outdated aicp copy, someone else's file, or the CLI itself isn't configured — the same view --config's Skills screen shows, without a terminal.

--install-skills --yes installs anything MISSING and upgrades anything older than the version aicp ships, exactly like the interactive Skills view — and it never touches a file aicp doesn't recognize. Only --force does that, and even then the original is moved aside to <name>.bak (numbered .bak.1, .bak.2, … so a second forced install never clobbers the first backup) before aicp writes its own copy.

Releasing

.github/workflows/release.yml runs on a v* tag. It repeats the full lint/test/build pass, installs the wheel into a throwaway venv, and refuses to upload unless that wheel reports the version being tagged — so a tag that disagrees with pyproject.toml fails before anything reaches PyPI. It then publishes with Trusted Publishing (OIDC — there is no API token stored in this repo) and attaches the wheel, sdist and SHA256SUMS to the GitHub release.

Checksums are generated after publishing on purpose: uv publish uploads everything in dist/, and SHA256SUMS is not a distribution.

A tag runs the workflow file as it existed at that tag, so a release that failed to publish can't be repaired by re-running the old tag — the fix isn't in that tree. Bump the version and tag again.

Configuration

Agent definitions live in src/aicp/agents.json, bundled with the installed package. Both aicp and aicp --config use this registry for executable lookup, invocation arguments, config directories, and skill installation/status. To maintain an agent, edit its entry here. Registry order supplies the default fallback order; aicp_cli_order still controls the user's preferred order.

Use ~/ paths and bare executable names for entries shared across macOS, Linux, and Windows. Python resolves ~ to the native home directory (USERPROFILE on Windows); forward slashes work on all three systems. Absolute paths are specific to the host OS; in JSON, a Windows path can use C:/Users/name/.codex or escaped backslashes (C:\\Users\\name\\.codex). Executable lookup uses PATH and, on Windows, PATHEXT; the runner handles both native executables and .cmd/.bat launchers. CI runs the registry integration tests and installed-wheel checks on all three operating systems.

Agent field Meaning
executable Binary name resolved on PATH, or an absolute executable path. Separate from the stable agent ID used in fallback order and history.
config_dir Absolute path or ~/-relative directory; home is resolved when used.
config_dir_env Optional environment variable overriding that directory (currently GROK_HOME).
skills_dir Relative directory inside config_dir where skills are installed.
skills Which vendored skills to install for this agent — any of safe-git-push, commit. Omitting commit is how claude keeps its own commands/commit.md.
memory_file Instruction filename used when adapting vendored skill text. Project directory references use the basename of config_dir.
args Argument array containing exactly one standalone {prompt}, replaced with the complete prompt as one argument. No shell evaluation.

The JSON envelope has version: 1 and an agents object keyed by stable agent IDs. Future supported settings can be added to each entry and the loader; adding a JSON field alone does not implement new behavior. Invocation defaults preserve agy's --new-project (use the current working directory), vibe's --trust (avoid an interactive trust prompt), and the existing MCP startup suppression flags.

This is packaged application data, not a per-user override file. Package upgrades replace it; neither repository-local files nor ~/.aicp/config.json can override agent execution settings. Ordinary user preferences continue to use the config file below.

Custom agents

You can add agents that are not in the built-in registry — or suppress built-in ones — without touching the bundled agents.json (which package upgrades replace). Your changes live in ~/.aicp/agents.json, which aicp merges over the built-in registry at startup: your entries win on conflict, and an entry with "disabled": true removes that agent from the chain.

You don't have to write that file by hand. aicp --agents edits it for you, and so does the Agents row of aicp --config:

aicp --agents                                   # list every agent and where it came from
aicp --agents disable grok                      # drop one from the chain
aicp --agents enable grok                       # put it back
aicp --agents set claude executable=/opt/claude # override one field
aicp --agents reset claude                      # forget your override

Like every other flag, the leading -- is optional: aicp agents disable grok.

Nothing is written unless the resulting registry passes the same validation the next run does, so a typo is refused with the file left exactly as it was — aicp can never be edited into a state where it won't start. Disabling an agent also removes it from a saved aicp_cli_order, because an order naming an agent that no longer resolves is rejected in full, which would silently reset a fallback order you picked on purpose. The last remaining agent can't be disabled.

Adding a new agent — example: MiniMax CLI. Every field is required for a name the built-in registry has never heard of:

aicp --agents set minimax \
  executable=minimax \
  config_dir='~/.minimax' \
  memory_file=AGENTS.md \
  skills_dir=skills \
  skills=safe-git-push \
  'args=--prompt,{prompt},--yes'

skills and args are comma-separated; a value that needs a literal comma has to go into the file by hand. The equivalent written out:

{
  "version": 1,
  "agents": {
    "minimax": {
      "executable": "minimax",
      "config_dir": "~/.minimax",
      "memory_file": "AGENTS.md",
      "skills_dir": "skills",
      "skills": ["safe-git-push"],
      "args": ["--prompt", "{prompt}", "--yes"]
    }
  }
}

A new agent joins the end of the fallback chain automatically. To put it first, use aicp --swap-ai, the AI CLI order row of aicp --config, or set the order yourself:

# ~/.aicp/config.json
{ "aicp_cli_order": "minimax copilot agy codex claude vibe grok" }

Or as an environment variable, for one run:

AICP_CLI_ORDER="minimax claude" aicp

For an agent that already exists, only the fields you provide are overridden; unspecified fields keep the built-in values. Running the chain only needs the executable on PATH, same as any built-in agent (see Fallback results); config_dir matters for skill installation — a newly added agent whose config_dir doesn't exist yet is skipped there until you set it up.

The config file lives at ~/.aicp/config.json (override the path itself with AICP_CONFIG, which — being the thing that names the file — can only be set as a real environment variable). Precedence everywhere is environment > config.json > hardcoded default. The file is a JSON object, parsed as data and never sourced or eval'd, written atomically and owner-only (0600) by --config/--swap-ai; only keys matching AICP_[A-Z0-9_]* case-insensitively with a string value built from letters, digits and / . _ : @ + - survive — everything else (an unknown key, a non-string value, a value outside that charset) is skipped individually, so one bad key never costs the rest of the file. An invalid value for a known key never aborts a run — it's reported on stderr and falls back to the default.

aicp always writes keys lower_case (aicp_do_commit, not AICP_DO_COMMIT) — every key in config.example.json and any new knob added in the future follows the same convention. Reading is case-insensitive, so an existing file with AICP_-cased keys still works.

A legacy ~/.aicprc (the pre-JSON KEY=value format) is migrated into ~/.aicp/config.json automatically, once, the first time aicp runs — the old file is left in place untouched, never deleted or rewritten.

See config.example.json for a ready-to-copy template.

Variable Default What it does
AICP_DO_COMMIT 1 Run the /commit step. 0 = only push what's already committed.
AICP_DO_PUSH 1 Run the /safe-git-push step. 0 = commit and stop.
AICP_LANG en Message language: en or zh-TW, everywhere including notifications.
AICP_CLI_ORDER registry order (copilot agy codex claude vibe grok out of the box) Fallback order, space-separated. A prefix is enough — any agent left out is appended after it, in registry order, so adding an agent never invalidates an order you already saved. An unknown or repeated name is refused outright and the default order is used.
AICP_TZ Asia/Taipei IANA zone name used to render commit timestamps. Anything else falls back to the default.
AICP_TZ_LABEL UTC+8 Cosmetic label shown beside those timestamps; not validated.
AICP_STEP_TIMEOUT (unset) Pins every CLI's per-step budget in seconds, skipping the formula and history below entirely.
AICP_TIMEOUT_BASE 180 Budget floor (seconds) — covers cold start plus a small prompt.
AICP_TIMEOUT_PER_FILE 15 Seconds added per changed or untracked file.
AICP_TIMEOUT_PER_100L 5 Seconds added per 100 changed lines in tracked files.
AICP_TIMEOUT_MAX 1800 Ceiling on the formula above. A CLI's own run history may still widen its budget past this — that's direct evidence it legitimately needs the time, not a guess.
AICP_TIMEOUT_HISTORY_LINES 500 How many recent timing-log rows are scanned when widening a budget from history.
AICP_TIMEOUT_HISTORY_MULT 1.3 Multiplier applied to a CLI's largest successful run when that exceeds the formula.
AICP_QUOTA_COOLDOWN 3600 Seconds a CLI that reported a quota/rate-limit signal stays skipped, across runs (state in ~/.aicp/quota.json). 0 switches the feature off; anything not a plain integer in 1..604800 falls back to the default.
AICP_SKIP_SECRET_SCAN (unset) 1 bypasses the pre-commit secret scan for one run — the documented escape for a false positive.
AICP_CONFIG ~/.aicp/config.json Which file this loader reads. Environment-variable only — a file can't rename itself.
AICP_TG_SEND ~/.claude/scripts/tg-send.sh The Telegram send script run at the end of a notification. Environment-variable only — refused if set in config.json.
AICP_TIMING_LOG ~/.aicp/timing.log Where per-CLI timing rows are appended (rotated at 5 MB, 5 kept). Environment-variable only — refused if set in config.json.

AICP_TG_SEND and AICP_TIMING_LOG are refused from config.json on purpose: both name a path that then gets executed (AICP_TG_SEND, run as a script) or written and rotated (AICP_TIMING_LOG, mkdir -p / >> / a rename). A config file is exactly the kind of thing that can arrive synced from someone else's dotfiles repo, so anything that becomes a command or a filesystem sink stays a real-environment-only decision — set it in your shell, not the file.

Safety

Before any AI CLI runs, aicp scans everything the run could commit for likely secrets: the added lines of the pending diff, every tracked file git reports as binary, and every untracked file — since aicp never runs git add itself, a brand-new file with a pasted secret is exactly the case that would otherwise slip through unscanned. A hit stops the run before any AI CLI is called, and only ever prints the file, line number and the pattern's name — never the matched text itself, so a real secret can't reach your terminal, a log, or a Telegram notification through this path.

Seven fixed patterns are checked (OpenAI-style sk-… keys, GitHub PATs and App tokens, AWS access key IDs, bearer tokens, and PEM private-key blocks) — deliberately prefix/format checks only, with no entropy heuristic: that was tried and rejected upstream as the main source of false positives (this project's own API-key-shaped test fixtures tripped it). A false positive is bypassed once with AICP_SKIP_SECRET_SCAN=1.

A file the scanner can't read as text — genuinely binary, or a UTF-16 .env full of NUL bytes — is never silently skipped: it's reported as "not scanned, verify manually" so an unreadable file never reads as a clean one.

Platform support

Tested in CI on macOS, Linux, and Windows, full test suite on all three — no platform is a reduced or best-effort target.

Platform Notes
macOS No caveats.
Linux No caveats.
Windows Every AI CLI ships as an npm .cmd/.bat shim, which Windows can't launch directly (CreateProcess doesn't honor PATHEXT and can't run a batch file itself) — aicp resolves the real executable and, for a shim, launches it through cmd.exe itself rather than shell=True, so no user-controlled text ever builds a shell command line. Ctrl+C is forwarded as CTRL_BREAK_EVENT rather than delivered directly (Windows has no "foreground process group" concept), so a CLI that ignores that signal may not stop as cleanly as it would elsewhere.

On POSIX, a per-CLI timeout signals the CLI process itself, not any grandchildren it spawned. On Windows, terminating the intermediate cmd.exe can orphan the CLI behind the shim; that high-priority defect remains open in TODO.md. Ctrl+C still targets the Windows process group.

License

MIT — see LICENSE.

Release files for aicp-cli 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aicp-cli 0.8.0
File Size Uploaded
aicp_cli-0.8.0.tar.gz 335.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aicp-cli 0.8.0
File Interpreter ABI Platform
aicp_cli-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 452.6 kB

Release files / aicp_cli-0.8.0.tar.gz

Download URL aicp_cli-0.8.0.tar.gz
Size 335.5 kB
Tags Source
SHA-256 checksum
How to use checksums
4fd87a861de980dabd1e71db9817638d89eab83a85f8a7d02161d37dcf38e572
BLAKE2b-256 checksum
How to use checksums
367cadf00fb0abc8938c2e7b1579441bb2a07ace33dfe5589e14277456c12793
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.16 {"installer":{"name":"uv","version":"0.12.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / aicp_cli-0.8.0-py3-none-any.whl

Download URL aicp_cli-0.8.0-py3-none-any.whl
Size 117.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
41ef34990f517d527fbb332a8b9a39c7bfd44983c0a8e66e80f3754c1b717d8d
BLAKE2b-256 checksum
How to use checksums
6df209465f7467349bb20fc3093c925f1734aaacadb78442b55115ffc7b91fbb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.16 {"installer":{"name":"uv","version":"0.12.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.12.0

2 release files

0.11.0

2 release files

0.10.0

2 release files

0.9.1

2 release files

0.9.0

2 release files

This release

0.8.0 This release

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page