Agent reference.
The CLI, in one page.
You don't need to read this. Your agent does. It is also served as /cli.md. emfirge is a thin client for the hosted engine. It signs in, tokenizes identifiers on your machine, sends one command, and prints the answer as JSON, or as short text with --human. The verbs follow an agent's workflow: context, branch, try, check, request, report. request is listed but not built yet.
Setup, loop and rules
Your human gave you emfirge. Set it up with them once, then do the rest yourself.
You run the commands. Your human does three things only they can do: approve the browser sign-in, create the read-only role in their AWS console, and give you the role ARN. Everything after that is yours.
| Step | Command | Who |
|---|---|---|
| Check it runs | npx -y emfirge guide | you (Node.js 20 or later; -y skips npx's first-run prompt) |
| Sign in | emfirge login | you start it; your human approves in the browser (GitHub or Google). It waits 5 minutes. If the browser does not open, show them the address it prints, then wait. |
| No browser (cloud session, CI) | EMFIRGE_API_KEY | your human makes a key on their own computer (emfirge key create) and sets it in your environment's secret settings. Skip login. See API keys. |
| Get the role | emfirge connect | you run it and show your human the link; they create the stack and copy the RoleArn from its Outputs. connect takes no ARN and does not detect the role. |
| First scan | emfirge context --refresh --role-arn ARN --region REGION | you, with the ARN they gave you. Ask which region; there is no default. Keep the ARN: check --evidence needs it again. |
| Know the account | emfirge context inventory | you |
Not signed in is exit code 2 and the CLI never waits for a browser by itself, so run login and wait for your human. Sign-in is kept in ~/.emfirge/credentials.json; you need your human again only if it expires. The sign-in page is served on 127.0.0.1 of the machine you run on, so it cannot be used from a remote sandbox: there, use an API key (API keys).
The loop
emfirge context inventory: ask what is true.emfirge branch open --analysis ID --name NAME: make a safe copy.emfirge try BRANCH ...: put your change on the branch.emfirge check BRANCH: read the facts and the unknowns.- Not what you wanted? Fix your own plan and go back to
try.emfirge branch rollback BRANCHclears the last change. emfirge request: ask the owner. Not built yet: ask your human in your own channel.- Apply the change yourself, with your own access. Emfirge never writes to the cloud.
emfirge report BRANCH: the hand-back for what you applied. Then tell your human what changed.
Rules
- Emfirge never writes to the cloud. You apply your own change, with your own access.
- Facts, not fixes. Emfirge will not tell you what to do. You decide.
- Never apply a change that did not pass. Emfirge is advisory and stops nothing, so this is on you.
- Unknown means not checked. It never means safe. Read every
unknownentry. - Names and IDs come back as tokens (
S3_001,NAME_003,TAG_001). Pass them back as they are. Emfirge turns them into real IDs on your machine. - Emfirge keeps no plan text. Apply your own original plan. The hand-back is tied to it by sha256.
check --policyandcheck --evidenceshow an estimate first and send nothing. Run them with--accept-estimate ID(and--approvefor policy) only after your human agrees.- Run
npx emfirge@latestonly when emfirge tells you to (exit code 6), never as a habit.
emfirge guide prints this guide from the package itself. Add --human for short readable output, and --help to any verb.
Install and sign in
Run it with npx. There is nothing to install. Two of these steps need your human.
npx emfirge login # sign in with GitHub or Google in your browser
npx emfirge connect # print the link that creates the read-only roleThe npm package is emfirge and the command is emfirge. Node.js 20 or later. It is one file with no dependencies. Agents add -y to skip npx's first-run prompt: npx -y emfirge context inventory. For a permanent command, npm install -g emfirge puts emfirge on your PATH.
| Command | What it does |
|---|---|
emfirge login | Sign in with GitHub or Google in your browser. Your human approves it there; waits 5 minutes. Keeps the session in ~/.emfirge/credentials.json. If EMFIRGE_API_KEY is set, it says so and uses the key. |
emfirge logout | Sign out on this device. |
emfirge connect | Print the one-click CloudFormation link that creates the read-only role, with the ExternalId and the next steps. Show the link to your human: they create the stack. When it is ready, they give you the role ARN from its Outputs; use it in emfirge context --refresh --role-arn ARN --region REGION. |
emfirge connect --link | Link your saved sign-in to an existing API-key project. Needs EMFIRGE_API_KEY. |
emfirge key create, key list, key revoke | Make, list and revoke API keys for your human's project, for where no browser can open. See API keys. |
emfirge guide | Print the agent guide: the loop, the rules, the exit codes. |
emfirge --help, emfirge help COMMAND, emfirge COMMAND --help | Help, from the server's current command list. |
emfirge --version | Print the version. |
Nothing here asks for AWS access keys. The only thing that reaches the account is the role from connect; see Security & privacy. The commands status, privacy, tokens and purge belong to the MCP adapter, not to emfirge.
API keys
Where no browser can open, your human gives you a key instead of a sign-in.
A cloud agent session or CI cannot finish emfirge login. Your human makes an API key for their own project, once, on their own computer after emfirge login, and sets it as EMFIRGE_API_KEY in your environment's secret settings. You do not make keys: a call made with a key cannot create one (HTTP 403, exit code 2, forbidden).
| Command | What it does |
|---|---|
emfirge key create [--name LABEL] | Make a key for the project. Prints the key once, with key_id, prefix and created_at. Needs a browser sign-in, not a key. A project can hold 10 active keys; the eleventh is exit code 5 (conflict). The label is up to 80 characters. |
emfirge key list | The project's keys: key_id, name, prefix (the first 8 characters after emf_), created_at, last_used_at and active. Never the key itself. |
emfirge key revoke KEY_ID | Stop that key at once. Get the id from key list. |
Where EMFIRGE_API_KEY is set, every command sends it and skips browser sign-in; emfirge login only says so. key list and key revoke also work with a key. A key works for the whole project, like the sign-in. It does not create the read-only role: your human still creates it and gives you the role ARN and a region. Never print the key into a chat or commit it.
Where the commands come from
The server owns the command list, so a new command works without a new install.
emfirge fetches its command list from the server (GET /cli/manifest, public, with no customer data, 3 second timeout), builds its help from it, parses your arguments by it, and sends the request each command describes. If the server cannot be reached, it uses the copy built into the package, and says so when a command is unknown. The copy and the server's list are the same document, pinned by a test.
Only login, logout, connect, guide and request are built in. A few manifest commands (try, and some modes of context and check) run code built into the CLI, because they need more than one request, a file or stdin. Run npx emfirge@latest only when emfirge tells you to: a command that needs a newer emfirge exits with code 6.
Today the list has 15 commands: context, branch open|fork|list|get|rollback|rollback-to|discard|compare, try, check, report and key create|list|revoke.
Global options and exit codes
JSON on stdout. Errors on stderr. One exit code per kind of failure.
| Flag or variable | Meaning |
|---|---|
--human | Short readable output instead of JSON. |
-h, --help | Show help for the command. |
--version, -v | Print the version and exit. |
EMFIRGE_API_KEY | An API key made with emfirge key create, for a cloud agent or CI where no browser can open. Skips browser sign-in. See API keys. |
EMFIRGE_BASE_URL | Backend API URL. Defaults to https://emfirge.cloud/api. |
EMFIRGE_PRIVACY | strict (default), balanced or off. Strict tokenizes every recognized AWS identifier before a model sees it. |
EMFIRGE_NO_BROWSER | Set to 1 and login does not open a browser; it prints the sign-in address. |
Output is one JSON document on stdout. An error is one JSON line on stderr, {"error":{"code","message","exit"}}, and stdout stays empty. Names and IDs in the answer are tokens such as S3_001; pass them back as they are and emfirge turns them into real IDs on your machine. Local state lives in ~/.emfirge/: your sign-in and the token map, which never leaves your machine.
Exit codes
| Code | Error code | Meaning |
|---|---|---|
0 | Ok. The command ran. A finding does not change this: check and try exit 0 whatever they report. | |
1 | usage | A bad command or option, a missing argument, or input the server rejected (HTTP 400 or 422). Also AWS CLI shorthand, and request (not_built). |
2 | not_signed_in, forbidden | Not signed in (HTTP 401) or not allowed (HTTP 403). Run emfirge login. |
3 | not_found | The branch, analysis or resource was not found (HTTP 404). |
4 | server_error, rate_limited | A server or network error, or a rate limit (HTTP 429). Try again later. |
5 | conflict | The branch changed since you read it, or 10 branches are already open on one base scan (HTTP 409). Read it again, or discard branches you no longer need, then retry. |
6 | update_needed | The server's command is newer than this install. Run npx emfirge@latest, then retry. |
context
Ask what is true before you change anything.
emfirge context QUESTION [--analysis ID | --branch ID] [--resource ID]
emfirge context --refresh --role-arn ARN --region REGION
emfirge context --findings | --attack-paths | --compliance --analysis ID
emfirge context --simulate "what if ..." --analysis ID| Question | What it answers |
|---|---|
inventory | What exists. |
resource | One resource: its type, tags and relationships. Needs --resource. |
depends_on | What a resource depends on. Needs --resource. |
dependents | What depends on a resource. Needs --resource. |
reachable | What the internet can reach. |
access | Who can access what, from the identity policies Emfirge has collected. |
data | Data stores and their recovery points. |
cost | Monthly list price of what is provisioned. |
live | Recent use. |
marks | Owner tags. emfirge.md is not built yet, so tags are all it shows. |
baseline | Counts for the whole snapshot. |
| Flag | Meaning |
|---|---|
--analysis ID | The analysis (scan) to ask about. Defaults to your latest. |
--branch ID | Ask about a branch instead of a scan. Not together with --analysis. |
--resource ID | A resource ID or token such as S3_001. |
--refresh | Run a fresh scan of your account. Needs --role-arn and --region. |
--findings | List the findings of a scan. Add --severity Critical|Moderate|Low to filter. |
--attack-paths | Paths from the internet to inside resources. |
--compliance | CIS AWS Foundations 1.5 or SOC 2 status. Add --framework cis-aws-1.5|soc2 to filter. Controls Emfirge does not evaluate are not_evaluated, never pass. |
--simulate "…" | Walk a what-if kill chain; the value is the question. |
Use one mode at a time. --findings, --attack-paths, --compliance and --simulate need --analysis. Every answer has facts, known (what was checked) and unknown (what was not, and why).
$ emfirge context data
{
"question": "data",
"source": { "kind": "scan", "analysis_id": "ANALYSIS_1", "branch_id": null,
"collected_at": "2026-10-01T00:00:00Z", "region": "ap-south-1" },
"facts": [
{ "resource_id": "NAME_002", "resource_type": "rds_instance",
"has_recovery_point": false, "recovery_resource_ids": [], "recovery_kinds": [] },
{ "resource_id": "NAME_004", "resource_type": "rds_instance",
"has_recovery_point": true, "recovery_resource_ids": ["NAME_005"],
"recovery_kinds": ["rds_snapshot"] }
],
"known": [ { "topic": "data_stores", "detail": "5 data stores assessed" } ],
"unknown": [ { "topic": "data_contents", "reason": "what a data store holds is not read; …" } ]
}For agents
- Run
contextfirst; do not infer from names. Facts come from your account's last scan, with what was checked and what was not. - Read
unknown. Unknown means not checked. It never means safe. source.collected_atis the age of the answer. Run--refreshbefore a change that depends on fresh state. A scan counts against a daily limit per AWS account; the reply says how many remain.--refreshscans through the read-only role.check --policyandcheck --evidencealso use it, and only after the owner accepts a cost estimate.
branch
A workspace for a change. Open, fork, compare, roll back, discard.
emfirge branch open --analysis ANALYSIS --name NAME
emfirge branch fork BRANCH --name NAME [--revision REVISION]
emfirge branch list [--analysis ANALYSIS]
emfirge branch get BRANCH
emfirge branch rollback BRANCH [--revision REVISION] [--expected-count N]
emfirge branch rollback-to BRANCH --count N [--revision REVISION]
emfirge branch discard BRANCH [--revision REVISION] [--expected-count N]
emfirge branch compare BRANCH BRANCH [BRANCH ...]| Subcommand | What it does |
|---|---|
open | Open a branch: a safe copy of a scan. With no changes it equals live. Never touches your cloud. |
fork | Copy a branch, with its changes, under a new name. |
list | List your branches. --analysis limits it to one base scan. |
get | Read one branch and its ordered changes. Never the base snapshot. |
rollback | Remove the most recent change. On a branch with no changes it does nothing. |
rollback-to | Keep the first --count changes and drop the rest. --count 0 returns to the base. |
discard | Discard the branch. It deletes the branch and everything stored with it (plans, evidence, policy checks). Nothing in your cloud changes. |
compare | Compare two or more branches. |
--revision is the branch_revision you last saw (a SHA-256). --expected-count is the change count you last saw. A stale one is rejected with HTTP 409, which is exit code 5. By default at most 10 branches can be open on one base scan; past that the server answers HTTP 409, which is exit code 5: discard branches you no longer need.
emfirge branch open --analysis ANALYSIS_ID --name close-ssh
emfirge branch fork BRANCH_ID --name close-ssh-v2
emfirge branch compare BRANCH_ID BRANCH_ID_2For agents
- One idea per branch. To try a different plan,
forkor open a new branch instead of stacking a contradicting change. - Several agents on one base: pass
--revisionfrombranch get. Exit code 5 means someone else changed the branch; read it again. - Only open branches take changes. Clean up with
discardwhen you are done.
try
Put a change on a branch, in whatever form you already have it.
emfirge try BRANCH --typed JSON
emfirge try BRANCH --aws-cli "aws ec2 ..." [--aws-cli "aws ..."]
emfirge try BRANCH --aws-api FILE|JSON|-
emfirge try BRANCH --terraform FILE|JSON|-
emfirge try BRANCH --cloudformation FILE|JSON|-
emfirge try --verify-fix --rule RULE --resource ID --analysis ID| Flag | Meaning |
|---|---|
--typed JSON | One typed change as JSON: {op, resource_type, resource_id, fields}. op is add, modify or delete. Add branch_revision or expected_change_count to refuse a stale branch. |
--aws-cli CMD | An AWS CLI command, written out in full. Repeat the flag for several, or pass a JSON list. |
--aws-api | AWS API calls as JSON: {"format":"aws-api-calls/1","calls":[...]}. |
--terraform | The output of terraform show -json PLAN. Emfirge never runs the plan. |
--cloudformation | The output of aws cloudformation describe-change-set. |
--verify-fix | Model the fix for one finding on a copy of a scan, with no branch. Needs --rule, --resource and --analysis. |
Give exactly one input. A JSON input is a file path, inline JSON, or - for stdin. AWS CLI shorthand (--target Arn=…,RoleArn=…) is refused before anything is sent, with exit code 1; pass the value as JSON. Emfirge keeps only a SHA-256 of a plan, never its text, so apply your own original plan.
For the four plan formats the answer lists what was applied, rejected (impossible input or a value AWS would reject), unmodeled (read but not modeled, so the verdict gets stricter) and unknown_fields (a value the plan left out, never guessed). It also carries a verdict (pass, warn or block, with reason codes), a verdict_id, the branch_revision and the hand-back. The resource types you can change are on Coverage.
$ emfirge try BRANCH_ID --aws-cli "aws ec2 authorize-security-group-ingress --group-id SG_001 --protocol tcp --port 22 --cidr 0.0.0.0/0"
{
"plan_id": "64ded399-…",
"input_sha256": "7304d5f4…",
"applied": [ "calls[0] ec2.AuthorizeSecurityGroupIngress" ],
"rejected": [], "unmodeled": [], "unknown_fields": [],
"verdict": {
"decision": "block",
"reasons": [
{ "level": "block", "code": "introduced_critical_finding", "resources": ["SG_001"] },
{ "level": "block", "code": "introduced_critical_exposure", "resources": ["EC2_001"] },
{ "level": "warn", "code": "new_exposure", "resources": ["EC2_001"] }
]
},
"verdict_id": "32b82c7e…",
"branch_revision": "dc62be73…"
}For agents
- Send the change you were already going to run. Do not translate it into the typed form.
- If the answer lists an unmodeled change, read it: the verdict cannot pass. Do not retry the same change.
- Use the exact ID or token you were given.
emfirge context resource --resource IDfirst when you are unsure.
check
Ask what the branch changed. Six answers, each with what was checked and what is unknown.
emfirge check BRANCH
emfirge check BRANCH --verdict | --diff
emfirge check BRANCH --policy [--approve --accept-estimate ID]
emfirge check BRANCH --evidence [--source S] [--accept-estimate ID --role-arn ARN]| Flag | Meaning |
|---|---|
| (none) | The six answers (data, exposure, money, availability, control, outside world), with checked and unknown for each, plus changed_settings. No verdict. |
--verdict | The older verdict: pass, warn or block, with reason codes. |
--diff | The diff against the base scan. |
--policy | Ask AWS IAM Access Analyzer about changed policies. It costs money (about $0.002 a policy, billed to your AWS account), so it prints an estimate first and sends nothing. Run it with --accept-estimate ID and --approve once the owner agrees. |
--evidence | Read CloudTrail and CloudWatch use of deleted resources. Estimate first. --source cloudtrail_events or cloudwatch_metrics, repeatable. Run it with --accept-estimate ID --role-arn ARN. |
A check never says pass, warn or block on its own. Each answer lists facts (what changed), checked (what was looked at) and unknown (what was not, and why). changed_settings lists every setting that changed, by name and never by value: judged by a check, or named as not judged. Not judged is not safe. How to read it is on How it works.
$ emfirge check BRANCH_ID --human
Branch check: BRANCH_ID (2 change(s))
- data: 0 fact(s), 4 unknown
- exposure: 1 fact(s), 1 unknown
fact: {"resource_id":"EC2_001","resource_type":"ec2_instance","change":"newly_exposed","protocol":"tcp","ports":[22,22],"so...
unknown (not checked, not safe): {"topic":"observed_traffic","reason":"observed traffic, host firewalls and application-level controls are not collect...
- money: 0 fact(s), 1 unknown
- availability: 0 fact(s), 3 unknown
- control: 0 fact(s), 2 unknown
- outside_world: 0 fact(s), 2 unknown
changed settings: 3 (1 judged by a check, 2 not judged)
Changed, not judged:
security_group SG_002: attached_instances
security_group SG_002: attached_toFor agents
- Read the facts, then read the unknowns and the changed settings. A check that did not run did not find nothing.
- A fact is about your change. Change the plan and retry on a new branch; do not argue with it or route around it.
--policyand--evidenceshow an estimate first and send nothing. Run them only after the owner agrees.
request
Ask the owner. Not built yet.
emfirge request BRANCH [--reason TEXT]The command is listed in emfirge --help, but it is planned for a later version. It makes no request: it exits with code 1 and a JSON error, not_built. Until it exists, ask the owner yourself, then use emfirge report after you apply.
For agents
- Do not wait on
request. Ask the owner in your own channel when a check shows facts they should see.
report
Say what you applied. For now it returns the hand-back, and can verify it.
emfirge report BRANCH [--plan PLAN] [--verify]| Flag | Meaning |
|---|---|
| (none) | The hand-back of the branch: the exact AWS API calls its typed changes stand for, their input_sha256, the base scan, the branch_revision, the verdict and a verdict_id. |
--plan PLAN | The hand-back of one plan from emfirge try. |
--verify | Send the hand-back back to the server, which checks the binding. |
The hand-back ties the exact calls to the verdict they were judged with. --verify answers {"ok": true}, or fails with exit code 5 and one reason_code: unknown_plan, input_hash_mismatch (these are not the calls that were judged), branch_revision_changed, base_snapshot_changed or verdict_changed. It checks the binding only. It does not read CloudTrail, and Emfirge never applies anything.
For agents
- Apply your own original change with your own access, then run
reportso the record shows what was judged. - If
--verifynames a mismatch, tell the owner. Do not edit the calls to make it match.
MCP adapter
The same engine, for agents that speak MCP.
Note: the npm adapter (@emfirge/mcp 0.2.3) is behind the repo (0.2.9) and an update is coming. The CLI is the supported path today.
@emfirge/mcp exposes 27 tools over stdio, so a client that cannot run a shell can still use emfirge. The CLI is built on the same handlers, so both paths share one engine and one privacy layer. The tool names are unchanged. It also carries the local commands the CLI does not have.
| Command | What it does |
|---|---|
npx @emfirge/mcp install | Wire emfirge into the MCP clients it detects. |
npx @emfirge/mcp uninstall | Remove it from those clients. |
npx @emfirge/mcp status | Show wired clients, the backend URL and the privacy mode. |
npx @emfirge/mcp privacy [strict|balanced|off] | Show or set the privacy mode. |
npx @emfirge/mcp tokens | List the local identifier-to-token mappings. |
npx @emfirge/mcp purge --role-arn ARN | Delete the data the backend holds for that AWS account and your local token map. Read what it removes on Security & privacy. |
| Verb | MCP tools behind it |
|---|---|
context | emfirge_context, emfirge_scan, emfirge_get_findings, emfirge_attack_paths, emfirge_check_compliance, emfirge_simulate_breach |
branch | emfirge_create_branch, emfirge_list_branches, emfirge_get_branch, emfirge_fork_branch, emfirge_discard_branch, emfirge_rollback_branch, emfirge_rollback_branch_to_count, emfirge_compare_branches |
try | emfirge_apply_change, emfirge_plan_verdict, emfirge_verify_fix |
check | emfirge_check, emfirge_branch_verdict, emfirge_branch_diff, emfirge_policy_checks, emfirge_in_use_evidence |
request | none |
report | emfirge_branch_handback |
connect, login, logout | emfirge_setup_help, emfirge_login, emfirge_logout, emfirge_link_project |
Or add it by hand, then restart the client.
{
"mcpServers": {
"emfirge": {
"command": "npx",
"args": ["-y", "@emfirge/mcp"],
"env": { "EMFIRGE_PRIVACY": "strict" }
}
}
}[mcp_servers.emfirge]
command = "npx"
args = ["-y", "@emfirge/mcp"]
[mcp_servers.emfirge.env]
EMFIRGE_PRIVACY = "strict"