Skip to main content

amca

One command that knows what to do in whatever directory you are standing in.

amca is a dispatcher for plugins based on the current directory, works out which of its plugins apply, and runs them. The behaviour lives in plugins.

# exaples
$ cd ~/code/renderer     # a C project
$ amca                   # with meson plugin installed and meson.build setup
[setup] … [compile] … [install] … [test] … [run]

$ cd ~/code/scraper      # a shell script
$ amca                   # autoscript plugin installed
[autoscript] sh /home/you/code/scraper/amca_auto_script.sh

Same keystroke everywhere. amca decides which plugin applies and hands it the arguments; it has no opinion about what a build is.


Requirements

Python 3.10 or newer. Nothing else — amca has no required runtime dependencies, deliberately, so a broken third-party wheel can never stop it from starting. Individual plugins may have their own.

Install

uv tool install --python 3.13 --with 'amca[all]' amca

--python pins a uv-managed interpreter so a distro Python upgrade cannot break amca. Alternatives:

pipx install 'amca[all]'
pip install --user 'amca[all]'

# to track main instead of the last release:
uv tool install --python 3.13 --with 'amca[all]' git+https://github.com/Delici0u-s/amca
extra gives you without it
tui arrow-key selection menus (InquirerPy) numbered stdin prompts
remote installing plugins from GitHub (requests) bundled plugins only
all both

Upgrade with uv tool upgrade amca or pipx reinstall amca. There is no build step and nothing to recompile after a system update.

Uninstall:

amca completions --uninstall     # first, while amca can still find its files
uv tool uninstall amca           # or: pipx uninstall amca
rm -rf ~/.config/amca ~/.local/state/amca

Two commands

amca ships two console scripts, split by lifetime:

amca run plugins, and everything scoped to a project or a single run
amcapl manage which plugins exist on this machine at all

They share global flags and configuration. amcapl is separate so amca --help stays short and so plugin management cannot be triggered by a stray argument during a build.

Command reference

amca [global flags] [command] [MARKER plugin-args …]

  (none) / run     run every enabled plugin that applies here
  new              create an amca root (.amca/) in this directory
  remove           delete the amca root
  root             show or manage root detection (show, ignore, unignore, clear-ignored)
  args PLUGIN      edit that plugin's per-project default arguments
  config           inspect or change settings
  plugins          list installed plugins and their markers
  completions      print, install or remove shell tab-completion
  doctor           check paths, config, plugins, tools; non-zero on a problem

amcapl [global flags] <command> [NAME …]

  list                 what is installed, and its marker
  install              fetch and enable
  enable | disable | toggle
  uninstall
  update               re-fetch (use after editing a local plugin source)
  call NAME -- ARGS    run one plugin directly, skipping should_load

Global flags: --config-dir --plugin-dir --depth --editor --marker-prefix --log-mode --log-level --log-prefix --on-error --on-missing --debug --version, plus --dry-run on amca. Flags apply to one run and never write to your config file — see Configuration.

Omit the names on any amcapl command and you get a picker. In the checkbox, space selects and enter confirms — enter on its own selects nothing.

Exit status: 0 success, 1 a plugin or command failed, 2 bad usage or bad configuration, 130 interrupted.


Plugins

A plugin decides whether it applies (should_load) and then does the work (load). amca runs every enabled plugin that applies.

Passing arguments to a plugin

Everything after a marker goes to that plugin, until the next marker:

amca ---example --show
amca ---autoscript deploy --dry

A marker is plugins.marker_prefix (default ---) plus the plugin's folder name, underscores written as dashes. amca plugins --markers prints the exact strings this directory accepts.

Naming a plugin runs only that plugin. With no markers, amca runs everything applicable — the usual case. Set plugins.marker_scope=all if you want a marker to add arguments without narrowing the run.

An unrecognised marker is an error, and it suggests the fix:

$ amca ---autoScr
amca: unknown plugin marker '---autoScr'
  did you mean: ---autoscript
  known markers: ---autoscript, ---meson

A bare -- stops marker parsing, for values that begin with the prefix:

amca config set plugins.marker_prefix ä

Avoid shell glob or history characters (* ? [ ] { } ~ !) in the prefix — your shell expands the marker before amca sees it. config set warns if you try.

Per-project default arguments

.amca/args/<plugin>.args, one argument per line, # for comments. They are placed before anything you type, so a project can pin a flag while a command-line flag still overrides it.

amca args meson        # opens it in $EDITOR
amca args meson --show

Where plugins live

Three different places, easy to conflate:

<config>/plugins/ installed — what actually runs. amcapl manages it; plugins.dir moves it.
plugins/amca_presets/ in this repo the three bundled with amca, so amcapl install works offline
anywhere you like yours

Your own plugins do not belong in this repo. Add a source:

amca config set plugins.sources builtin,/home/you/my-amca-plugins
amcapl install cargo
amcapl update cargo            # re-copy after editing

While iterating, skip the copy entirely:

amca --plugin-dir ~/my-amca-plugins ---cargo build

Writing a plugin

A plugin is a directory containing plugin.py with exactly one subclass of amca.api.Plugin:

from amca.api import Plugin, PluginContext, PluginError
from amca.core import proc


class cargo(Plugin):
    description = "cargo build/run"

    def should_load(self, ctx: PluginContext) -> bool:
        # Cheap — called for every enabled plugin on every invocation.
        return ctx.project_dir_info().has_file("Cargo.toml")

    def load(self, ctx: PluginContext) -> int:
        if ctx.dry_run:
            ctx.log.log("would run cargo build")
            return 0
        cargo = proc.resolve_tool("cargo", hint="install rustup")
        return proc.call([cargo, "build", *ctx.args], cwd=str(ctx.project_dir))

PluginContext carries args, working_dir, root, plugin_dir (your private per-project directory, already created), dirs, log and dry_run.

Import amca.api directly — amca is an installed package. Do not vendor copies of its internals. Raise PluginError for a clean message with no traceback. Return an exit status; 0 or None means success.

Subdirectories work and are namespaced per plugin, so two plugins can both have an _impl/ without colliding:

my-plugin/
    plugin.py
    _impl/
        __init__.py

Start from the example plugin rather than from one of the real presets:

amcapl install example
amca ---example --show     # prints every PluginContext field and what it is for

Bundled plugins

Three, installed on demand with amcapl install NAME. None of them is privileged; they are ordinary plugins that happen to ship inside the wheel.

applies when does
example never — call it directly sixty commented lines to copy: selection, --dry-run, PluginError, tool resolution, logging
autoscript amca_auto_script.* is present runs it, forwarding your arguments
meson meson.build carries the amca variable block setup → reconfigure → compile → install → test → run
autoscript — details

Runs amca_auto_script[.sh|.bash|.zsh] (.ps1/.bat/.cmd on Windows) from the working directory, falling back to the project root.

amca ---autoscript --new       # create one (asks which shell)
amca ---autoscript build       # forwarded to the script
amca ---autoscript -- --help   # forward --help verbatim
meson — details

Needs meson, which needs ninja and a compiler.

amca ---meson                 # whole pipeline
amca ---meson compile         # one step
amca ---meson -s              # clean first
amca ---meson -n t -n e       # skip test and run
amca ---meson -Ab "--buildtype=debug" -Ae "--verbose"
amca ---meson --print-template > meson.build

Applies only to a meson.build carrying the amca variable block, so it stays inert in meson projects that are not yours:

amca_var__meson__version_behaviour = '2.0.1'
amca_var__meson__build_dir         = 'build'
amca_var__meson__executable_name   = 'myapp'
amca_var__meson__install_dir       = '../compiled'   # relative to build_dir

reconfigure re-runs meson only when the set of source files changes — ninja handles content changes. .vscode/launch.json and .clangd are kept pointing at the current build directory.


Configuration

One JSON file. Four layers, lowest priority first:

built-in default  →  config file  →  AMCA_* env var  →  command-line flag

Only amca config set writes to disk. Command-line flags apply to that run and nothing else.

config state (logs, caches, first-run marker)
Linux/BSD $XDG_CONFIG_HOME/amca~/.config/amca $XDG_STATE_HOME/amca~/.local/state/amca
macOS ~/Library/Application Support/amca ~/Library/Caches/amca
Windows %APPDATA%\amca %LOCALAPPDATA%\amca

Override with --config-dir / $AMCA_CONFIG_DIR and $AMCA_STATE_DIR. amca config path prints the one actually in use.

amca config list --origin      # every setting, its value, and which layer won
amca config list --changed
amca config get plugins.marker_prefix
amca config set plugins.marker_prefix '+++'
amca config unset core.editor
amca config describe log.mode  # type, default, env var name
amca config edit

--origin answers "I changed it and nothing happened": it shows whether an env var is overriding your edit, whether the key is real (unknown keys are reported, not ignored), and which file is actually in use.

Hand-editing the JSON is fully supported — config set is a convenience. Nested and dotted keys both work. Every setting has an env var: plugins.marker_prefixAMCA_PLUGINS_MARKER_PREFIX.

All settings
key default
core.debug false extra diagnostics and full tracebacks
core.greet false print a greeting on every invocation
core.editor $VISUAL/$EDITOR/nano editor for amca args
root.folder_name .amca marker directory identifying a project root
root.search_depth 5 how far up to look for it
root.ask_to_create true offer to create one (TTY only)
root.ignored_paths [] where not to ask; amca root ignore
log.mode console console, file, both, silent
log.level INFO INFO, SUCCESS, WARN, ERROR, FATAL
log.prefix none none, minimal, simple, normal, verbose
plugins.dir <config>/plugins where installed plugins live
plugins.enabled [] which may run; amcapl enable
plugins.marker_prefix ---
plugins.marker_scope selected selected or all
plugins.on_error continue or abort
plugins.on_missing warn ignore, warn, abort
plugins.announce_loaded false log each plugin as it runs
plugins.sources builtin, github:… where amcapl install looks

External tools

export AMCA_TOOL_MESON=/opt/meson/bin/meson
export AMCA_TOOL_NINJA=samu

Security

A plugin is arbitrary Python that runs as you, with no sandbox. amca imports plugin.py and calls it; it does not inspect, restrict or review what that code does. Treat installing a plugin exactly like curl … | sh.

Concretely:

  • amcapl install copies a directory from a source and enables it. The defaults are builtin (inside the wheel you already trust) and a GitHub repository. Anything you add to plugins.sources — a URL, a local path — becomes code that runs on your next amca.
  • should_load runs for every enabled plugin on every invocation, before you have named anything. An enabled-and-installed plugin therefore executes in every directory you type amca in, not only where it applies.
  • .amca/args/<plugin>.args is read from the project directory, so cloning an untrusted repository that contains a .amca/ directory hands that repo influence over the flags your plugins receive. Run amca args <plugin> --show before running amca in a repo you did not write.
  • Shell completion shells out to amca on every keypress. That is amca itself, by absolute path — nothing from the project directory.

amca doctor lists every installed plugin and where it came from.

Shell completion

Usually nothing to do. The first time you run amca interactively it writes a completion script to your shell's per-user completion directory and says so once. bash and fish pick it up on the next shell.

zsh only reads directories on $fpath, so it needs one extra line:

amca completions --install --rc     # adds a delimited block to ~/.zshrc
exec zsh
amca completions --status           # per shell: installed? active? which helper?
amca completions --uninstall        # removes the file(s) and the rc block
amca completions bash > somewhere   # just print it
export AMCA_NO_AUTO_COMPLETION=1    # opt out entirely

A wheel cannot run code at install time (PEP 427), so pip, pipx and uv tool have no way to register completions — only a distro package can. Doing it on first run is the closest equivalent that works for every install method. Nothing is added to a shell rc file unless you pass --rc.

bash and fish autoload by command name, so amcapl gets its own file alongside amca's; zsh binds both from a single #compdef line.

Using an alias? The generated script calls amca by absolute path, so a venv install off PATH still completes — but the alias needs registering:

amca completions --install --command a3
# zsh also needs, in your rc:  compdef _amca a3

Completion is dynamic: plugin names, markers and config keys come from amca at completion time, so installing a plugin or changing the prefix takes effect immediately. That costs one amca invocation per keypress. The helpers are scriptable on their own:

amca plugins --names
amca plugins --markers
amca config list --keys

When something is wrong

amca doctor

Checks paths, which config layer each non-default setting came from, root resolution, every plugin's health, external tools, and the subprocess environment. Exits non-zero when it finds a problem. It is the right thing to paste into an issue.

Upgrading from amca 2.x
amca config migrate --dry-run     # show what would be imported
amca config migrate

Keys were renamed and several boolean pairs became enums:

2.x 3.x
args.plugin_prefix plugins.marker_prefix
generic.plugin_path plugins.dir
generic.exit_on_plugin_error plugins.on_error = continue|abort
generic.exit_on_plugin_not_found plugins.on_missing = ignore|warn|abort
logging.print_loaded plugins.announce_loaded
extreamly_important.greet_user core.greet

Then remove the old install before deleting the old checkout — the uninstaller is the only thing that knows to strip the # >>> amca PATH >>> block from your shell rc:

python install_uninstall_update.py uninstall --keep-config

Existing plugins keep working: the loader detects the old five-argument should_load(amca_root_dir, amca_root_plugin_dir, working_dir, dir_parser, args) and adapts it. Port them when convenient.

If you upgrade by extracting a release over an old checkout, note that unzipping does not delete removed files — amca doctor will tell you if a stale src/amca/presets/ is left behind.

Why 3.0 exists. 2.x shipped as a PyInstaller --onefile binary rebuilt at install time. That put a private copy of CPython on LD_LIBRARY_PATH for every child process, so after a distro Python upgrade the system meson loaded amca's stale libpython and died with internal Python C API version mismatch. 3.0 is a normal Python package, which makes that class of bug structurally impossible. Along the way: CLI flags no longer write to your config file; plugin markers have one definition instead of two disagreeing ones; root discovery no longer runs at import time; plugins import amca.api instead of vendoring internals; and two plugins can have identically named submodules without shadowing.

Development

git clone https://github.com/Delici0u-s/amca && cd amca
pip install -e '.[all,dev]'

pytest -q                    # unit tests, under a second
python tests/e2e.py          # end-to-end matrix, real subprocesses
python tests/e2e.py --list   # the matrix without running it
python tests/e2e.py -k markers -v
ruff check src tests plugins
mypy

Layout:

src/amca/              the application
plugins/amca_presets/  bundled plugins — content, not application code
tests/

tests/test_regressions.py is one test per defect that shipped in 2.x. Add to it rather than deleting from it. tests/e2e.py runs the real console scripts against throwaway config and project directories; cases marked ! exercise a mistake, hostile argv, or a broken component, and there are more of those than happy paths. tests/fixtures/probe/ is a plugin that dumps its whole PluginContext as JSON and can be driven into any failure mode with PROBE_* environment variables. tests/test_completion_scripts.py sources the generated shell scripts and drives the completion functions for real — emitting a script is not evidence that it completes anything.

Platform support

Linux fully tested
macOS code paths tested by simulation, not executed
Windows code paths tested by simulation, not executed

tests/test_platform.py patches os.name / sys.platform and reloads the affected modules to exercise the non-Linux branches: config directories, the .exe suffix, the autoscript interpreter table, non-POSIX shlex splitting, colour detection, read-only file deletion. That verifies the branches are correct and reachable — not that Windows behaves as expected.

Windows caveats: plugin folder names are matched case-sensitively, so keep them lowercase; ANSI colour needs Windows Terminal or a console accepting ENABLE_VIRTUAL_TERMINAL_PROCESSING; autoscript does not offer an extensionless script, since such a file is not executable there.

License

GPL-3.0-or-later. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

amca-3.0.1.tar.gz (111.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

amca-3.0.1-py3-none-any.whl (102.4 kB view details)

Uploaded Python 3

File details

Details for the file amca-3.0.1.tar.gz.

File metadata

  • Download URL: amca-3.0.1.tar.gz
  • Upload date:
  • Size: 111.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.7

File hashes

Hashes for amca-3.0.1.tar.gz
Algorithm Hash digest
SHA256 f3013bb45ae4b1728b5381c982244404b06cbe5973b81909231af36182526a2f
MD5 113e64c4efc6ef056e00e4a81825cca5
BLAKE2b-256 909993001395a6bb977ba19908044ebe81c104240fa210eaacb1684ee4c6a912

See more details on using hashes here.

File details

Details for the file amca-3.0.1-py3-none-any.whl.

File metadata

  • Download URL: amca-3.0.1-py3-none-any.whl
  • Upload date:
  • Size: 102.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.7

File hashes

Hashes for amca-3.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 891e9527d542cc23675167a203d897bbd26ad480b723b960de44c310c6cf4d1a
MD5 c5edf444715dc31d81f2bc8e3a9f0b36
BLAKE2b-256 82913e3e52dbb0d5f980798292baca1bfbf8665379bc09d3a8467788ae500f7b

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

3.0.1 This release

2 files

3.0.0

2 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