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.
- Install · First run · Two commands · Command reference
- Plugins · Writing a plugin · Bundled plugins
- Configuration · Security · Shell completion
- Troubleshooting · Development · Platform support
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_prefix → AMCA_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 installcopies a directory from a source and enables it. The defaults arebuiltin(inside the wheel you already trust) and a GitHub repository. Anything you add toplugins.sources— a URL, a local path — becomes code that runs on your nextamca.should_loadruns for every enabled plugin on every invocation, before you have named anything. An enabled-and-installed plugin therefore executes in every directory you typeamcain, not only where it applies..amca/args/<plugin>.argsis 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. Runamca args <plugin> --showbefore running amca in a repo you did not write.- Shell completion shells out to
amcaon 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f3013bb45ae4b1728b5381c982244404b06cbe5973b81909231af36182526a2f
|
|
| MD5 |
113e64c4efc6ef056e00e4a81825cca5
|
|
| BLAKE2b-256 |
909993001395a6bb977ba19908044ebe81c104240fa210eaacb1684ee4c6a912
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
891e9527d542cc23675167a203d897bbd26ad480b723b960de44c310c6cf4d1a
|
|
| MD5 |
c5edf444715dc31d81f2bc8e3a9f0b36
|
|
| BLAKE2b-256 |
82913e3e52dbb0d5f980798292baca1bfbf8665379bc09d3a8467788ae500f7b
|