# ActionForge (CLI prototype)
ActionForge is a working prototype of a multi-rule, config-file-driven
automation daemon: you describe many independent, named "watch a folder
and run a command" rules in a single JSON file, and ActionForge runs all
of them at once, in one process, each with its own directory, its own
file-extension filter, and its own shell command. It is the "Pro" tier of
the same product line as the sibling tool MacroDeck. MacroDeck watches
exactly one folder with one rule supplied entirely as CLI flags
(`macrodeck watch
--run ""`); ActionForge instead loads a
config file that can define any number of rules and treats each one as an
independently tracked, independently reportable unit of work, so a
problem with one rule (for example its watch directory not existing yet)
never affects the others — this per-rule fault isolation, not a bigger
CLI surface, is the real difference in mechanism between the two tools.
Text expansion, clipboard variables, and global keyboard/mouse macros are
explicitly NOT implemented here: they require OS-level UI-automation
APIs (accessibility hooks, global input hooks, clipboard managers) that
differ per platform and go well beyond a stdlib-only file-watching CLI.
They are recorded as roadmap items; see ../plan.md for the full product
plan.
## Build from source
Requires Go 1.24+, no external dependencies.
go build -o actionforge .
Cross-compile for another platform:
GOOS=windows GOARCH=amd64 go build -o actionforge.exe .
GOOS=darwin GOARCH=arm64 go build -o actionforge .
## Usage
actionforge run --config [--interval 2s] [--once] [--run-existing] [--log events.log]
actionforge validate --config
actionforge -h | --help
### `run`
Loads the config file and polls every rule's watch directory. Each rule
tracks its own "known files" state completely independently of every
other rule (same new-or-changed-since-first-seen semantics as MacroDeck):
files that are already present when a rule starts watching do NOT trigger
by default, only files that appear or change afterwards. If a rule's
watch_dir does not exist (or stops existing) at poll time, ActionForge
prints a per-rule error to stderr and retries that rule again on the next
poll -- it does not crash, and it does not affect any other rule. When
the directory later appears, that rule establishes a fresh baseline and
resumes normal triggering with no restart required.
Flags:
--config Path to the JSON rules file (required)
--interval Poll interval, e.g. 500ms, 2s, 1m (default 2s)
--once Single pass over every rule's directory, then
exit. Every file currently present in each
rule's watch_dir is treated as a trigger for
this one pass (same as --run-existing, but only
for this pass). Useful for testing or
cron-style invocation instead of running as a
long-lived daemon.
--run-existing In continuous (non --once) mode, treat files
already present in each rule's watch_dir at
startup as triggers too, instead of only using
them to seed the baseline. Applies to every
rule in the config.
--log Append one JSON object per triggered event to
this file (JSON Lines format): rule_name, file,
command, timestamp.
For every trigger, ActionForge prints:
[rule-name] /absolute/path/to/file -> expanded command
and then executes the command through the OS shell (`/bin/sh -c` on
Unix, `cmd /C` on Windows).
### `validate`
actionforge validate --config
Parses the config file and checks that it is valid JSON, that every rule
has a non-empty name, watch_dir, and run command, and that no two rules
share the same name. It does not start watching anything. Prints "OK"
and exits 0 if the config is valid; otherwise it lists every specific
problem found and exits non-zero.
## Config file format
{
"rules": [
{
"name": "screenshots-to-archive",
"watch_dir": "/tmp/actionforge-demo/incoming",
"extensions": [".png", ".jpg"],
"run": "cp {file} /tmp/actionforge-demo/archive/"
},
{
"name": "logs-cleanup-alert",
"watch_dir": "/tmp/actionforge-demo/logs",
"extensions": [".log"],
"run": "echo new log: {file}"
}
]
}
- `name` (required): a unique label for the rule, used in log output and
the JSON event log.
- `watch_dir` (required): the folder this rule watches. Not recursive.
- `extensions` (optional): a list of file extensions (e.g. ".png") this
rule reacts to. Omit or leave empty to match every file in the
directory.
- `run` (required): a shell command to run when a matching file is added
or changed. `{file}` is replaced with the triggering file's absolute
path.
## Roadmap (not implemented in this prototype)
- Text expansion (typed shortcuts expanding into snippets/templates)
- Clipboard variables (referencing recent clipboard history in a rule's
command)
- Global keyboard/mouse macros (recording and replaying input system-wide)
These all require OS-specific UI-automation APIs (accessibility
frameworks, global input hooks, clipboard managers) rather than plain
filesystem polling, so they are out of scope for this stdlib-only,
cross-platform CLI prototype. See ../plan.md for how they fit into the
broader product plan.
## Prebuilt binaries
See ../downloads/ for prebuilt binaries (Windows/macOS/Linux) and
CHECKSUMS.txt for their SHA-256 hashes. These are unsigned indie builds --
Windows SmartScreen and macOS Gatekeeper will warn on first run, which is
expected until a code-signing certificate is in place.