Scripting with cam
Every machine-readable surface is --json with the
schema-v1 contract: pure JSON on stdout,
human channels on stderr, the error envelope (not a crash dump) on handled
failures.
Shell patterns
Branch on the exit code first, parse second:
if cam status --json | jq -e '.active.managed'; then
echo "live login is managed by cam"
fi
# headroom of every enabled account, worst first
cam list --json | jq -r '
.accounts[] | select(.enabled) |
[.name, (.usage.fiveHour.pct // "n/a")] | @tsv' | sort -k2 -rn
# which setting is overriding the default?
cam config list --json | jq '.settings[] | select(.isSet)'
# scheduled evaluation; outcomes are exit codes, not text
cam auto --once
case $? in
0) echo "switched" ;; 3) echo "wanted to switch but blocked" ;;
esac
Streaming the auto loop
cam auto --json is JSONL, one event object per line. Filter by event:
cam auto --json | while IFS= read -r line; do
jq -e '.event == "switch"' <<<"$line" >/dev/null && \
notify-send "cam switched" "$(jq -r '.to' <<<"$line")"
done
Unknown event kinds and unknown fields are part of the contract:
consumers ignore them so additive releases never break a parser.
Stability rule
schemaVersion bumps only on breaking changes. New fields arrive silently
inside 1; write parsers that accept, not parsers that enumerate.