@danypops/armada
v0.5.2
Published
Cross-platform desired-state control for local Vehicle fleets
Readme
Armada
Armada reconciles a user-scoped fleet of local Vehicle daemons through systemd user units, macOS LaunchAgents, or Windows Task Scheduler. Native service managers own the processes; Armada is not a resident supervisor.
Install
Requires Node.js 22.
npm install --global @danypops/armadaManifest
The default manifest is ~/.config/armada/armada.json on Linux, ~/Library/Application Support/armada/armada.json on macOS, and %APPDATA%\Armada\armada.json on Windows.
{
"schemaVersion": 1,
"vehicles": [
{
"name": "example",
"version": "1.0.0",
"executable": "/absolute/path/to/example",
"arguments": ["serve"],
"handlePath": "/absolute/path/to/handle.json",
"restart": {
"policy": "on-failure",
"delayMs": 1000,
"maxAttempts": 3,
"windowMs": 60000
},
"readiness": {
"timeoutMs": 5000,
"pollIntervalMs": 100
},
"resources": {
"memoryLowPercent": {
"value": 3,
"enforcement": "required"
},
"memoryHighPercent": {
"value": 30,
"enforcement": "required"
},
"maximumMemoryPercent": {
"value": 37.5,
"enforcement": "required"
},
"cpuWeight": {
"value": 100,
"enforcement": "required"
}
}
}
]
}Credentials and secret-like material are rejected. Executables, working directories, and handle paths must be absolute.
On systemd, memoryHighBytes maps to MemoryHigh= as the pressure boundary and maximumMemoryBytes maps to MemoryMax= as the final defense. Percentage envelopes use memoryLowPercent, memoryHighPercent, and maximumMemoryPercent for a protected baseline, pressure boundary, and hard ceiling relative to installed physical memory. cpuWeight controls relative CPU access under contention without preventing idle-capacity bursts. Byte and percentage memory boundaries cannot be mixed, and boundaries must be ordered. Percentages scale with host size; they do not track currently free memory or reserve aggregate fleet capacity. Other native managers reject required resource controls and warn for optional controls they cannot enforce.
Commands
armada plan --json
armada reconcile --json
armada status --json
armada doctor --jsonIntegrations can atomically update one Vehicle without replacing the fleet:
armada upsert --vehicle-file ./vehicle.json --json
armada reconcile --jsonDuplicate cleanup is consequence-planned and requires the current hash:
armada cleanup example --json
armada cleanup example --approve <planHash> --jsonRemove an Armada-owned service and its manifest declaration with:
armada remove example --jsonForce one named Vehicle to stop+start now, bypassing plan/manifest-hash drift detection entirely -- for a caller that knows something changed underneath a fixed entry point (e.g. a dependency bump) even though the declared spec didn't:
armada restart example --jsonUse --manifest <path> with any command to select a different manifest.
Report a Vehicle's own tool/operation usage metrics (see @danypops/vehicle-server's own metrics support) directly from its SQLite file -- no daemon round-trip, no manifest lookup needed, works for any Vehicle name whether or not Armada manages it:
armada metrics example --json
armada metrics example --since 2024-01-01T00:00:00Z --until 2024-02-01T00:00:00Z
armada metrics example --tool tasks.create --source server
armada metrics example --group-by toolName,errorCode --limit 100--since/--until accept either epoch milliseconds or an ISO-8601 date
(--since inclusive, --until exclusive). --group-by accepts a
comma-separated list of toolName, vehicleName, source,
callerSessionId, outcome, errorCode, day, hour. --limit bounds
grouped output from 1 through 1,000; JSON output includes the effective limit,
truncation state, and fixed latency buckets. Reports "no metrics recorded
(yet)" rather than an error for a Vehicle that hasn't opted into metrics or
has none recorded yet.
Testing integrations
@danypops/armada/testing provides an isolated real-registrar harness backed by a stateful mock native controller and mock Vehicle applications. Readiness can complete automatically, wait for markReady(), or return a timeout without touching the host service manager. status() projects mock application state through Armada's real fleet-status builder.
import { createArmadaTestHarness } from "@danypops/armada/testing";
const harness = await createArmadaTestHarness({ readiness: "manual" });
try {
const registering = harness.registrar.register(vehicle);
await harness.waitForEvent("ready-wait:example");
harness.application("example").markReady();
await registering;
} finally {
await harness.dispose();
}Development
From the Vehicle repository root:
bun install
bun run check
bun test packages/armada/test