Get started
The Immiscible CLI
One command governs the agent in your project. Sign in through the browser, create the agent and its rule, write .env, install the Claude Code hook and make a live test call. Works for people and for AI coding agents running non-interactively.
The fastest way to put Immiscible in front of an agent:
npx immiscible initIt signs you in if you are not, finds what your project uses, creates the agent and its rule, adds IMMISCIBLE_URL and IMMISCIBLE_AGENT_KEY to .env, installs the Claude Code hook where there is Claude Code, prints the code for your SDK, and ends with a live test call:
✓ Governed by Immiscible: Invoice agent (Acme)The CLI is the immiscible package on npm. It needs Node 22.13 or later and has no dependencies. To have an immiscible command without npx, install it once with npm install -g immiscible.
Or install it with one line, which checks Node, installs the package and shows the welcome card:
curl -fsSL https://immiscible.fly.dev/install.sh | shirm https://immiscible.fly.dev/install.ps1 | ieximmiscible about shows the card again: the version, the credits and the links. It is drawn only in a terminal; on a pipe or in CI it prints plain text, and --json gives the same facts as data. The card draws the Cardinal squares in dots, with the dot field thickening towards the right; under 98 columns it goes compact, and under 74 it is plain. Set IMMISCIBLE_CARD=compact or IMMISCIBLE_CARD=classic to choose, and IMMISCIBLE_NO_MOTION=1 to turn off the animation.
This is the developer CLI. The repository also has an operator CLI, immiscible-server (run in a checkout as npm run admin -- <command>), which runs a server rather than talking to one: backups, integrity checks, the demo seed. They are different programs, and immiscible-server init points you back here.
#See it first, with no account
npx immiscible tryFrom 0.2.0. In under a minute, offline: a made-up finance agent asks three times, and the fake Immiscible server the SDKs test against, started on 127.0.0.1 with its own signing key, decides. Looking up an invoice is allowed; paying £1,250 to a supplier it has not paid before is held, and you approve or deny it at the prompt; paying a lookalike of a known supplier is denied. Then try saves the receipt, runs immiscible verify on it, says what just happened, and ends on immiscible init. The fake imitates a rule; it is not the real policy engine. Nothing leaves the machine, and the fake stops when try ends. Without a terminal, pass --yes to approve the held payment.
#Check a receipt
immiscible verify receipt.jwt --keys keys.jsonFrom 0.2.0. Checks a signed receipt offline, with the same verifier as the SDK: the Ed25519 signature, the key id, the type and the expiry. It prints what was allowed: the action, the amount and where it went, and whether a person approved it. --keys takes a saved copy of the issuer’s /.well-known/immiscible-keys.json (nothing is fetched) or its URL; without it, the keys come from your server and the receipt must be that server’s. The receipt can be a file, the token itself, or - for stdin. It needs no account and no agent key, and exits 12 when the receipt is not valid.
#Sign in
immiscible loginThe CLI shows a one-time code and opens your browser at /app/device. Sign in if you are not, check the code matches your terminal, pick the workspace and choose Allow. The terminal says who you are signed in as. This is the OAuth 2.0 device authorization grant (RFC 8628), with PKCE: the code in the browser is useless without the secret the CLI kept.
The token is stored in ~/.config/immiscible/credentials.json (or under $XDG_CONFIG_HOME), readable only by you (mode 0600), one entry per server. It acts for you in the workspace you picked and can only read agents and status and add agents with a rule from your templates: it never approves anything or changes an existing rule. It lasts 90 days. End it with immiscible logout, from your sessions in the console, or by signing out everywhere or changing your password; an owner or admin can end any CLI sign-in to the workspace. Your workspace’s sign-in rules (address allowlist, single sign-on, two-factor, an administrator ending your sessions) apply to it on every use.
immiscible login --url https://immiscible.your-company.com | sign in to a server you run yourself (or set IMMISCIBLE_URL) |
immiscible login --token imc_... | store a token you already have, after checking the server accepts it |
IMMISCIBLE_TOKEN=imc_... immiscible status | use a token without storing it: what CI does |
immiscible whoami | who, which workspace, which server, and where the token came from |
immiscible logout | revoke this machine’s token and forget it |
immiscible token create --name ci | a CI token, shown once (see below) |
The server is chosen in this order: --url, IMMISCIBLE_URL, IMMISCIBLE_URL in the project’s .env, the server you last signed in to, then https://immiscible.fly.dev.
#Govern the agent in a project
immiscible init- Detects the project, from files only:
package.json(openai,@anthropic-ai/sdk,ai,langchainand@langchain/*,@openai/agents),pyproject.toml,requirements*.txtorPipfile(openai,anthropic,langchain,openai-agents), a.claude/directory orCLAUDE.md, MCP configs (.mcp.json,.cursor/mcp.json,.vscode/mcp.json), and x402 or wallet SDKs. - Asks the minimum: the agent’s name, and what it does, from the same purposes as the console’s Add an agent (each shown with the rule it gets in your workspace). New agents start at the workspace’s starting tier, intern unless an owner has chosen junior, as everywhere.
- Creates the agent and its rule. When the agent acts for you and your workspace has another owner, the rule goes to them: Sent to another owner to confirm; payments start once they do. Until then the agent is connected and everything it asks for is refused.
- Writes
.env: addsIMMISCIBLE_URLandIMMISCIBLE_AGENT_KEY. A value already there is never replaced without asking; a.envthat points at another server stops init before anything is made (use--urlto keep it, or--force). In a git repository it adds.envto.gitignore(creating the file if need be), because.envholds the agent key; it shows the line and asks first, and--no-gitignoreleaves.gitignorealone. - Installs the Claude Code hook in a Claude Code project, after showing the change to
.claude/settings.jsonand asking: the hook file goes in.claude/hooks/immiscible-claude-code-hook.mjs, and onePreToolUseentry is added with the full matcher and|| exit 2, so it fails closed. Your other settings and hooks are left as they are. - Prints the code for the SDK it found.
- Makes a live test call through the gate as the agent and prints
✓ Governed by Immiscible: <agent> (<workspace>). While the rule still waits for another owner it prints! Waiting for another owner to confirm the ruleinstead, and exits 10 with"ok": false. The test is marked as a test and tidies up after itself: nobody is notified, and a question for a person is cancelled at once, so nobody has to decide it.
Running init again changes nothing that is already right: the key in .env is checked and reused, .env and .claude/settings.json are left byte for byte, and the test call reuses its idempotency key while the agent’s rules are unchanged. Once a rule changes (another owner confirms it, say), the test is made afresh, so the answer printed is always the current one. If the key in .env is no longer accepted, init offers to replace it.
The hook entry it writes:
{
"matcher": "Bash|Write|Edit|MultiEdit|NotebookEdit|WebFetch|mcp__(?!immiscible__(check_action_status|explain_decision|spend_summary|find_waste|unwatched_keys)$).*",
"hooks": [
{
"type": "command",
"command": "node --env-file-if-exists=\"$CLAUDE_PROJECT_DIR/.env\" \"$CLAUDE_PROJECT_DIR/.claude/hooks/immiscible-claude-code-hook.mjs\" || exit 2",
"timeout": 60
}
]
}Immiscible’s own read-only MCP tools (check_action_status, explain_decision, spend_summary, find_waste and unwatched_keys on the immiscible server) are left out, so the hook never asks Immiscible about asking Immiscible; its tools that act still go through. A new agent’s default rule (General tasks) lets provably read-only calls (ls, git status, git diff, git log, reading a file) go ahead without a person; each is still decided and recorded. An intern asks before anything that writes, runs or deletes something. Once the agent is past its intern stage, edits, test runs and builds inside its project go ahead without a person (localWrites: "allow-after-intern"); the hook sends the project, CLAUDE_PROJECT_DIR or the working directory, and a call that names a path outside it asks (from 0.2.0: an older hook does not say which project, so those calls still ask). At every standing and under every rule, these still ask: a destructive command (rm -rf, git push --force, git reset --hard, git rebase, curl ... | sh), any git push, a deploy, a publish, a package install from the network, anything run with sudo, and a change to .claude/settings.json or git’s hooks; a secrets file leaving the machine is refused. The rule says so on the agent’s page; to have a person sign off reads too, replace it with one that sets readOnly: "ask" or leaves it out; to have a person approve every edit and test run, replace it with one that sets localWrites: "ask".
Beside the hook, init adds Claude Code deny rules to the same file, shown in the same diff: Bash(rm -rf:*), Bash(rm -fr:*), Bash(sudo rm:*), Bash(git push --force:*), Bash(git push -f:*), Bash(git reset --hard:*), Bash(git clean -f:*), Read(./.env) and Read(./.env.*). Claude Code refuses those itself, before any hook runs and without the network. Deny rules you already have are kept, in your order.
The hook reads IMMISCIBLE_URL and IMMISCIBLE_AGENT_KEY from the project’s .env (a variable already in the environment wins). More on what it sends and how it decides: Claude Code and Cursor.
| Flag | |
|---|---|
--name <name> | the agent’s name |
--purpose <purpose> | pays_invoices, books_travel, handles_refunds, buys_software, answers_customers or other |
-y, --yes | accept defaults and confirm changes; required when not at a terminal |
--hook, --no-hook | install the Claude Code hook without .claude/, or never |
--no-test | skip the live test call |
--no-gitignore | leave .gitignore alone |
--force | replace IMMISCIBLE_URL in .env when it points elsewhere |
--dir <path> | the project (default: the current directory) |
#Govern every project on a machine
immiscible install claude-code [--scope user|managed] [--transport command|http] [--key <agent key>] [--dry-run]init governs one project; install claude-code puts the hook in front of every Claude Code session on the machine. --scope user (the default) merges it into ~/.claude/settings.json; --scope managed, run as an administrator, writes Claude Code’s managed settings file, which people cannot override, and sets allowManagedHooksOnly so only managed hooks run. The default command transport fails closed; --transport http has Claude Code post each event to your server instead, and lets a call go on if the server cannot be reached at all. It shows the diff and asks first (--yes when there is no terminal), --dry-run writes nothing, and a second run changes nothing. After copying the hook it runs node <hook> --self-test. Pair it with the Coding agent baseline rule. More in the Claude Code fleet pack.
| Flag | |
|---|---|
--scope <scope> | user or managed |
--transport <transport> | command (fails closed) or http |
--key <key> | an agent key to write into the settings’ env; left out, each person’s environment supplies IMMISCIBLE_AGENT_KEY |
--gateway <url> | send Claude Code’s model traffic through your Immiscible gateway (ANTHROPIC_BASE_URL) |
--dry-run | show the change, write nothing |
-y, --yes | write without asking; required when not at a terminal |
#Hooks for Codex, Cursor, Windsurf and Gemini CLI
immiscible install codex # or cursor, windsurf, gemini; for you
sudo immiscible install codex --scope managed # for everyone on this machine
immiscible install gemini --dry-run # show the change, write nothingCopies one hook, coding-agent-hook.mjs (Node, no dependencies), and adds one entry to the agent’s own hook configuration, so the agent asks Immiscible before each shell command and MCP tool call, and before each file write where it has that event. The same rules decide as for the Claude Code hook. It fails closed: when Immiscible cannot answer, the call is refused, and the command ends in || exit 2 so a missing file or a missing node blocks it too. Every other setting and hook in the file stays as it is, an older Immiscible entry is replaced in place, and running it again changes nothing. After copying the hook it runs node <hook> --self-test, and only then changes the agent’s configuration.
| Agent | --scope user (the default) | --scope managed |
|---|---|---|
| Codex | ~/.codex/hooks.json | requirements.toml (/etc/codex on macOS and Linux), with allow_managed_hooks_only = true |
| Cursor | ~/.cursor/hooks.json | the enterprise hooks.json (/Library/Application Support/Cursor, /etc/cursor, C:\ProgramData\Cursor) |
| Windsurf | ~/.codeium/windsurf/hooks.json | the system hooks.json (/Library/Application Support/Windsurf, /etc/windsurf, C:\ProgramData\Windsurf) |
| Gemini CLI | ~/.gemini/settings.json | the system settings.json (/Library/Application Support/GeminiCli, /etc/gemini-cli, C:\ProgramData\gemini-cli), with hooksConfig.enabled |
The hook reads IMMISCIBLE_URL and IMMISCIBLE_AGENT_KEY from the environment, then from hook.env beside it (written with your server’s address, and the key with --key). Cursor and Windsurf start hooks from the app rather than your shell, and Gemini CLI gives hooks a reduced environment, so hook.env is how they find the key. When a decision needs a person, Cursor asks you at the keyboard with the approval link; Codex and Gemini CLI wait up to four minutes for someone to approve in Slack, Teams, email or the console; Windsurf documents no hook timeout, so it refuses with the link and you run it again once approved. More in hooks for other coding agents.
| Flag | |
|---|---|
--scope <user|managed> | for you, or for everyone on the machine (run as an administrator) |
--key <key> | an agent key, written to hook.env (left out, each person’s environment supplies it) |
--dry-run | show the change and write nothing |
-y, --yes | write without asking; required when not at a terminal |
With --json: { ok, target, scope, config, configState, hookFile, envFile, command, diff, changed, selfTest, warnings }. install is in the next CLI release; until it is published, run it from a checkout as node packages/immiscible-cli/bin/immiscible.mjs install codex.
#Check the setup
immiscible doctor| Check | Fails when |
|---|---|
| Node.js | warns below 22.13 (the hook command needs --env-file-if-exists) |
| Server | /healthz does not answer |
| Clock | this machine is 60 seconds or more from the server (signed receipts allow 60); warns from 5 |
| Signed in | the token is not accepted (not being signed in only warns) |
| Environment | IMMISCIBLE_URL or IMMISCIBLE_AGENT_KEY is missing from .env and the environment; warns when the shell’s value differs from .env (the hook uses the shell’s, so doctor checks that one) |
| Agent key | the key is not accepted, or the agent is stopped; warns when it has no rule yet |
| Claude Code hook | in a Claude Code project: not installed, not the full matcher, the command does not end in exit 2, a timeout above 60, or it does not refuse when Immiscible cannot be reached (doctor runs it against an address nothing answers on); warns that the hook is out of date when the file differs from the one this CLI ships or (from 0.2.0) the one your server serves |
| .gitignore | warns when .env holds the key and is not ignored |
Each problem comes with a fix and a link here. The exit code is 0 when nothing failed and 7 when something did.
#What needs you
immiscible statusWhat waits for approval (with a link to each), today’s decisions since 00:00 UTC (allowed, asked a person, refused; init’s own connection tests are not counted), and this month’s AI spend measured by the gateway and agent payments, in your workspace’s books currency at the newest European Central Bank rate.
#Evidence for an auditor
immiscible evidence ai-act --out pack.zipDownloads the EU AI Act deployer evidence pack for the workspace you are signed in to, from 0.3.0 of the CLI (not yet published on npm): a zip with pack.json and a readable SUMMARY.md, or the JSON alone when --out ends in .json. Owners, admins, security admins and auditors can export it; other roles get exit code 6. It never replaces a file unless you pass --force, and it exits 12 when the pack reports that the ledger did not verify, so a scheduled export notices.
#What your agents can touch
npx immiscible check # this project and your home directory; nothing is sent
npx immiscible check --upload # also a report link from the findings, for seven daysThe local half of the AI check. It reads files and runs nothing: the MCP servers in .mcp.json, .cursor/mcp.json, .vscode/mcp.json, ~/.claude.json, ~/.cursor/mcp.json, Claude Desktop, Windsurf and Gemini CLI, and what each can do (make payments, run shell commands, write to a database, send email); Claude Code’s permission settings (Bash(*), bypassPermissions, MCP tools allowed without asking); and provider keys in .env files, MCP configs and shell config (~/.zshrc, ~/.bashrc, ~/.profile and the like), with whether git ignores the file. No sign-in is needed.
Checked ~/code/hollis-agents (nothing left this machine)
Agents OpenAI Agents SDK, LangChain
MCP 3 servers · stripe can make payments · postgres can write to a database
Claude Claude Code may run any shell command without asking (Bash(*) is allowed)
Keys 2 provider keys in .env · 1 in shell config
Riskiest first
1 stripe (MCP, .mcp.json) can make payments with a live secret key, so with no limit but the account's, and nothing asks a person first.
2 Claude Code may run any shell command without asking (Bash(*) is allowed), in .claude/settings.json.
3 postgres (MCP, .mcp.json) can write to a database, and nothing asks a person first.A key is never printed or sent. It is shown as its provider, its prefix and last four characters (as the provider’s own console shows it) and a fingerprint, sha256: and the first 12 hexadecimal characters of its hash, so you can tell two keys apart without seeing either. --upload sends only the findings, the sentences above, to POST /api/check/upload: no key, redacted or not, no file contents, and no full path (a file outside the project is named from your home directory, such as ~/.zshrc). It cannot see whether a file is already committed (that needs git itself) or what a key may do at its provider. With --json: { ok, exitCode, agents, mcp, claude, env, keys, findings, uploaded }. The exit code is 11 when something is high risk (a payment tool that never asks, any shell command allowed, a key in a file git does not ignore) and 0 otherwise.
#What your agents did
npx immiscible scan # the last 7 days; nothing is sent
npx immiscible scan --since 30d --html report.htmlReads the session history coding agents already keep on your machine, from 0.3.0 of the CLI (not yet published on npm), and needs no account.
| Agent | Read from |
|---|---|
| Claude Code | ~/.claude/projects/*/*.jsonl (or $CLAUDE_CONFIG_DIR/projects), and the hooks and permissions.defaultMode in ~/.claude/settings.json and each project’s .claude/settings.json and settings.local.json |
| Codex | ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl (or $CODEX_HOME/sessions), and approval_policy and sandbox_mode in ~/.codex/config.toml |
| Gemini CLI | ~/.gemini/tmp/*/chats/session-*.json, and the hooks and approval settings in ~/.gemini/settings.json |
| Cursor, Windsurf | noted when installed, not read: both keep their history in a database whose format is not documented |
It reports sessions by agent and repository; commands run, flagging force pushes, rm -rf, terraform apply or destroy, kubectl and package publishes; files read and written, flagging reads of .env, keys, *.pem and cloud and registry credentials; outbound calls and the domains reached; a secret read followed by a network call in the same session, the shape of a secret leaving the machine; cost per session from the recorded token counts at list prices (a model without a published price is shown as not priced, never guessed); and the permission modes and hooks in effect, including bypass modes and hooks that are not Immiscible’s. It leads with the three most important findings, one sentence each, then the sections. An agent that is not installed is skipped with a note.
It also looks for secrets the agents’ own history files hold in plain text (agents write tool output to their transcripts word for word: a cat .env, a token in a failed deploy’s output): private keys, AWS, GitHub, Slack, npm, Stripe, OpenAI, Anthropic, OpenRouter and Google keys, by their published shapes. Each distinct secret is counted once, reported by kind and folder only, and the finding says to rotate them. If your git email is at a company domain, it counts the other people at that domain who committed to these repositories in the last 90 days, from git on your machine, and shows the number; --no-team turns that off.
No prompt text, file contents or secret values are printed or written, in any format: only paths, command names, domains and counts, and the output says so at the top. A command is reduced to the programs it runs, the secret paths it reads and the hosts it reaches; the command line itself is never shown. --html <file> writes the same report as one self-contained page with no script and no remote resource. With --json: { ok, exitCode, local, privacy, window, sources, totals, byAgent, byRepo, commands, files, network, mcp, exfiltration, permissions, sessions, transcripts, team, findings }. The exit code is 11 when something is high risk (a secret read then the network, a live secret in the history files, a force push, approvals bypassed, a SessionStart hook that is not Immiscible’s) and 0 otherwise.
#Fix it in one command
npx immiscible guard # every coding agent on this machine, local rules, no account
npx immiscible guard --dry-run # every change, nothing written
npx immiscible guard --off # put every file back exactly as it wasThe other half of scan, from 0.3.0 of the CLI (not yet published on npm): guard puts one fail-closed hook in front of each coding agent it finds (Claude Code, Codex, Cursor, Windsurf, Gemini CLI), with the same hooks install writes. With no account the hooks decide on your machine, sending nothing anywhere, from a short set of rules:
| Refused | force pushes to main, master, release/* or production; rm -r of the root or home directory; a secret file and a network or upload command in one call; disk wipes; network or shutdown lines added to shell start-up files |
| Asked | publishing a package or image; terraform, pulumi, kubectl and helm changes; curl | sh; sudo; history rewrites; dropping a database; edits to agent configuration (.claude/settings.json, hooks.json, .mcp.json), shell start-up files and CI workflows; and any network call after the session read a secret file |
| Allowed | everything else |
Claude Code and Cursor ask you at the keyboard. Codex, Windsurf and Gemini CLI cannot ask from a hook, so a call that needs a person is refused with the way to go ahead (run it yourself if you meant it). Before a call that deletes or overwrites files in a git repository, the hooks take a checkpoint, so undo can put the files back.
For Claude Code, guard also keeps the settings honest during a session, from 0.3.0 of the CLI. At the start of a session the hook notes the hooks, broad permission rules, bypass mode and MCP switches in the user, project and local settings. Claude Code reloads a settings file that changes mid-session and runs the ConfigChange hook first; a change that adds a hook, removes Immiscible’s, turns hooks off, allows Bash(*) and the like, turns on bypass permissions or adds an MCP server is kept out of the session, with the reason and “if you made the change, restart Claude Code to load it”. That is how a planted hook, such as the one the Shai-Hulud worm wrote into .claude/settings.json, or a script the agent ran gets in. Managed settings cannot be blocked and are not judged. A project hook new to the machine is named once when a session starts. Hook commands are kept only as a hash and the program’s name, never their arguments. A session’s “read a secret” mark is kept in ~/.immiscible/state as the file’s short name, never its contents, for a day.
--connect --key <agent key> points the same hooks at your Immiscible server instead, so a named person approves in Slack, Teams, email or on the phone and every decision is signed. --off puts back every file guard changed, from ~/.immiscible/guard.json: a file it created is removed, a file it changed is restored byte for byte, and a file someone has changed since guard wrote it is left alone and named. --agents claude-code,codex limits it to some agents; --scope managed writes each agent’s managed settings, for everyone on the machine. With --json: { ok, mode, scope, dryRun, state, agents, refused, asked, changed, selfTest }.
#Undo what an agent deleted
npx immiscible undo # the checkpoints of the last 7 days
npx immiscible undo 8b38f79ca2 --dry-run # what would change
npx immiscible undo 8b38f79ca2 # put the files backUndo before approve, from 0.3.0 of the CLI (not yet published on npm). Before a call that deletes or overwrites files in a git repository (rm, git clean, git reset --hard, git checkout --, git restore, find -delete, edits to agent configuration), the guard’s hooks take a checkpoint: every tracked and untracked file git does not ignore, kept as a commit under refs/immiscible/checkpoints/ in that repository. It takes about a tenth of a second in a repository of a thousand files. Nothing is pushed, because git pushes branches and tags and never this namespace, and checkpoints older than 7 days are deleted as new ones are taken. IMMISCIBLE_CHECKPOINTS=off turns them off.
When Claude Code or Cursor asks you to approve such a call, the question ends “If you approve, it can be undone: npx immiscible undo <id>”. A question about something that reaches past the machine, such as a publish, a deploy or the network, ends “This one cannot be undone from here.” Codex, Windsurf and Gemini CLI refuse rather than ask, so a refused call takes no checkpoint.
undo <id> shows what will change and asks, then takes a checkpoint of how things are now, so the undo can itself be undone. It writes the files back byte for byte with their modes, removes files made since, and restores the index, so staged work stays staged. Commits, branches, ignored files and anything outside the repository are left as they are. Pass --yes when there is no terminal. With --json: { ok, id, repo, command, changes, headMoved, restored, undoWith }.
#Replay a session
npx immiscible replay # the sessions of the last 7 days
npx immiscible replay 3f2a9c1d # one session in full, by the start of its id
npx immiscible replay 3f2a9c1d --html run.html # the same, as one self-contained pageThe flight recorder for your coding agents, from 0.3.0 of the CLI (not yet published on npm). It joins two records already on your machine, the session history Claude Code, Codex and Gemini CLI keep and the decision log guard’s hooks write, into one timeline per session: every command, file read and write and fetch in order, with the decision the guard made on each and the rule behind it. A call the guard refused is marked as not run, and a refused call the agent never got to make still appears, on its own line.
The hooks write one file a day to ~/.immiscible/decisions (IMMISCIBLE_LOG_DIR to move it, IMMISCIBLE_LOCAL_LOG=off to stop it). Each line carries the SHA-256 hash of the line before, so replay notices a line removed or edited, names the file and line, and exits 12. Commands are shown with credentials redacted, by their known shapes and by how random they look; prompt text and file contents are never shown. Nothing leaves the machine and no account is needed. With --json, { ok, local, chain, sessions } for the list and { ok, local, chain, session } for one.
#Add the MCP server
immiscible mcp # every client
immiscible mcp --client claude-code # or cursor, vscode, windsurf, codex, geminiPrints the one-line command or the config entry that adds Immiscible’s MCP server to each client, for the server you use, and changes nothing. The configs read the agent key from IMMISCIBLE_AGENT_KEY (which init writes to .env) or sign in with OAuth; the key itself is never printed. For Cursor it also prints a one-click install link. With --json: { ok, url, keyVariable, clients: { <id>: { title, command, file, config, note } } }. More in add the MCP server.
#In CI and AI coding agents
Every command works without a terminal. Nothing prompts: input comes from flags, and a command that would need to ask stops with exit code 4 and says which flag to pass. Colour is off when stdout is not a terminal or NO_COLOR is set, and there is no spinner.
export IMMISCIBLE_URL=https://immiscible.fly.dev # or your own server
export IMMISCIBLE_TOKEN=imc_... # from token create --name ci, shown once
immiscible init --yes --name "Release agent" --purpose other --json
immiscible doctor --jsonWith --json, stdout carries one JSON object and nothing else: ok, exitCode, and the command’s result (for init: the agent, the rule or the proposal waiting for a second owner, what changed in .env, the hook and its diff, the snippet, and the test call; for doctor: every check with its status, fix and docs link). Errors are { "ok": false, "exitCode": 3, "error": { "code", "message", "fix" } }. immiscible help --json describes every command, flag and exit code as data. login --json prints two lines: the code to show a person first, then the result.
For CI, make a token for the job rather than reusing your own sign-in: immiscible token create --name ci prints a CI token once. It acts for you in the workspace you are signed in to, with your CLI scopes or fewer (--read-only leaves out adding agents), and expires in 90 days or sooner (--days). immiscible token list and immiscible token revoke <id> manage them; owners and admins can also make one in the console, beside service tokens, and see and revoke every CLI sign-in there.
#Exit codes
| Code | Meaning |
|---|---|
| 0 | done |
| 1 | unexpected error (a server error, a file that could not be written) |
| 2 | usage: an unknown command or flag, or a bad value |
| 3 | not signed in, or the token is no longer accepted: run immiscible login |
| 4 | input needed and this is not a terminal: pass the flags it names |
| 5 | the server could not be reached |
| 6 | the server refused: a role, a rule, a plan limit |
| 7 | doctor: a check failed |
| 8 | login: denied in the browser, or the code expired |
| 9 | init: the test call did not come back governed |
| 10 | init: done, and the rule waits for another owner to confirm |
| 11 | check or scan: something high risk was found |
| 12 | verify: the receipt is not valid (altered, expired, an unknown key or another issuer); evidence: the ledger did not verify; replay: the decision log’s hash chain is broken |
The API the CLI uses is in the reference under Developer CLI.