@fnmoa/cli
v0.9.18
Published
moa — talk to a workspace from a terminal.
Maintainers
Readme
@fnmoa/cli
moa is the command line for fnMoa: a workspace that runs coding agents
(Claude Code, Codex and others) as a team on your GitHub repository. It
triages issues, writes the change, reviews it, runs the checks, and merges.
You use the CLI to:
- install a fnMoa workspace on this machine, a Vercel Sandbox or Cloudflare:
moa initwrites a folder you can read and fill, andmoa deploydeploys it; - look after it:
moa deploy --dry-run(the plan),moa status,moa down; - work with it: give agents work, follow runs, talk to the Manager, ship an issue to a merged pull request.
npx @fnmoa/cli init machine # or: npm install -g @fnmoa/cli, then `moa init machine`Node 22 or newer. fnMoa is proprietary software and needs a licence, which is free for one workspace. See Licence.
Quick start
Make an account. Go to https://fnmoa.locusgraph.com, sign in with GitHub, choose Start free on Plan and billing, and copy your key from Account → Licence key.
Write the folder.
moa init machine # or: moa init cloudflare --domain example.com, moa init vercel cd fnmoa-machine| Target | Folder | Needs | |---|---|---| |
machine|compose.yaml,.env| Docker with Compose. The workspace on 127.0.0.1:8787, the console on :3100. | |cloudflare|wrangler.jsonc,worker.js,Dockerfile,secrets.env|npx wrangler@4 login, your domain on Cloudflare, Workers Paid, Docker. | |vercel|sandbox.jsonc,secrets.env|vercel login(orVERCEL_TOKEN), Docker the first time. |Every folder also has a
README.mdwith each step, bymoaand by hand, a.gitignorethat leaves the secrets out, andmoa.json.Fill the secrets on the web. Sign in at https://fnmoa.locusgraph.com/account/secrets and fill each token there:
GH_TOKEN, an agent key, the console sign-in, and the rest, each with a note on how to get it. They are sealed in your browser with a passphrase and kept in your account's vault;moa deploypulls them in (the passphrase is asked once per machine).moa initasks you to approve a sign-in in the browser and writes this folder's licence (MOA_LICENCE) itself;--no-browserskips that, andFNMOA_LICENCE_KEYdoes the same job. The folder (secrets.env,.envon a machine, written 0600) keeps only what belongs to it: the owner token, the install id, the console's cookie secret, the licence, and a claude or codex login. Who may sign in to the console isMOA_LOGINSin the vault: GitHub usernames, comma separated, every one an owner; they sign in with GitHub through fnmoa.locusgraph.com. A folder from 0.7.x moves its secrets to the vault once withmoa secrets import.Plan, then deploy.
moa deploy --dry-run # what would be made (+), changed (~) or kept (=), and what is missing (!) moa deploy # makes it, deploys it, and logs `moa` in to itmoa deployrecords what it made in.moa/state.json, so running it again only changes what changed. On Cloudflare it makes the D1 database and R2 bucket and writes their ids intowrangler.jsonc.
codex on a ChatGPT plan
Run codex login on your machine, then: on Cloudflare and Vercel, copy
~/.codex/auth.json to codex/auth.json in the folder (moa deploy puts it in
place once); on a machine, put it in .env as MOA_CODEX_AUTH (the README has
the one-liner).
Looking after it
moa status [dir] # the workspace, the folder's owner token, the console
moa deploy [dir] --dry-run # what a deploy would change; changes nothing
moa deploy [dir] # apply the folder again
moa init <target> . --update # move the folder to this CLI's release; secrets kept
moa stop [dir] # stop what the folder deployed; every piece of data kept
moa start [dir] # start it again on the same data, and wait for the workspace
moa down [dir] # delete what moa deploy made, and nothing else; asks [--yes]down deletes only what .moa/state.json says moa deploy made, by id. A
database or bucket that was there before is left alone.
stop ends the compute and keeps the data: on Cloudflare the container is
destroyed and stays down (the workspace answers 503, the console a page saying
so) while D1, R2 and the Worker stay; on a machine it is docker compose stop,
container and volumes kept; on Vercel the servers end and the persistent sandbox
stops, its disk kept. start brings it back and prints the URLs; on Cloudflare
the cold start takes a minute or two. moa status says "stopped" for a stopped
Cloudflare deploy. In a deploy folder moa stop means the folder; with a job id
it stops that job.
Logs are the platform's own: docker compose logs -f on a machine,
npx wrangler@4 tail fnmoa-workspace on Cloudflare. A Cloudflare container
sleeps after its idle hours, and a Vercel sandbox stops at the end of its
session; the next request, or moa deploy, starts it again — a moa stop stays
until moa start.
To update fnMoa, update the CLI, then:
moa init <target> . --update && moa deployUsing a workspace
Log in to one
moa deploy logs you in to the workspace it deploys. To use one someone else
runs:
moa login https://workspace.example.com --token <token> # a token the owner gave you
moa login https://workspace.example.com --github # sign in with GitHub
moa whoami # which workspace, which tenant
moa use <profile> # switch between saved workspaces--github is for someone the owner has added on Console access. It prints a
GitHub device code, waits while you approve it, and saves a token of your own,
with your role and your daily spending cap. Removing you on Console access
stops it at once.
Check it is ready
moa doctor ok runs agents sqlite, v0.4.0
ok agent CLIs claude, codex
NO repository none configured — pipelines need MOA_REPO
NO github login gh has no token there — run `gh auth login` where the workspace runsExit 0 when it is ready, 2 when it is not.
Ship an issue
moa ship 41 # issue #41 → pull request → reviewed → mergedDeclare a team and give it work
moa agent create planner --cli claude --role "You scope the work. You never write code." --peers builder
moa agent create builder --cli claude --role "You write the code and its tests." --peers reviewer
moa agent create reviewer --cli codex --role "You read diffs and name defects."
moa run builder "fix the retry bug in src/http.ts" --watch
moa team work planner,builder,reviewer --watch
moa ask planner builder "which retry flag?" # and wait for the answer
moa delegate planner builder "implement the retry fix"--peers is who an agent may open a thread with. Replying inside a thread it is
already part of needs no permission.
Follow what is happening
moa watch # every job in the workspace, as it changes
moa jobs # what is running now
moa runs # what ran, newest first [--failed] [--state running]
moa logs <runId> --watch # every line a run produced, live
moa spend --since 24h # what it cost, and what asked for it
moa inbox # who is waiting on an answer
moa delegations --blocked # what is stuck, and the command to unstick itCtrl-C stops the work the command started and waits for it to stop. Press it
twice to leave the work running; moa jobs finds it again.
Talk to the Manager
The Manager is the agent that plans and starts work for you, the same one as on the console's chat page.
moa manager say "start #41 with the core team" # and wait for its answer
moa manager proposals # what it proposes, each with its id
moa manager approve <id> # say yes, and run it now
moa manager dismiss <id> # say no
moa manager watch # what is running, and how it is goingsay waits up to --timeout <s> (default 600). Past that it exits 2, and the
Manager's answer is in the chat when it has one.
Choose where it may work
The Manager and the Lead touch only the allowed repositories, and a pipeline starts only on one. None allowed is none. Changing the list is an owner's.
moa repos allowed # the repositories it may touch
moa repos allow acme/web # one the GitHub login here can push to
moa repos disallow acme/web # take it back; the Manager stops watching itConnect Envoy
Owners only. moa pair makes a one-time code and prints the line to send in
Envoy; it works once, for ten minutes.
moa pair # the /specialist add … line for Envoy
moa pair --repo acme/web,acme/api --cap 20 # only these repos, at most $20
moa pair list # who is paired, and the opt-in
moa pair same-owner on --owner nasim # your yes in Envoy counts here
moa pair same-owner off # every yes is given in fnMoaWith no --repo, the agent's tasks may act on every repository the Manager
watches. --owner is the part after @ in your agent's name (nasim for
envoy@nasim). Without it, same-owner on takes a paired agent's owner, or
the workspace's own. The name an agent signs with is matched without regard to
case.
For scripts
Exit codes
0 it did what it was asked
1 it ran and failed — a red build, a CLI that would not start
2 a rule stopped it — a budget, a policy, a bounced message, a timeout
3 the command was typed wrong, or the token was refusedJSON
--json writes the answer to stdout and nothing else. Warnings go to stderr.
moa agent list --json | jq -r '.agents[] | "\(.name)\t\(.cli)\t\(.status)"'
moa run builder "say ok" --json | jq '{result: .result.result, cost: .result.costUsd}'
moa jobs --json | jq -r '.jobs[] | select(.state == "running") | .kind'
moa ship 41 --json | jq -r '.result.outcome'
moa doctor --json | jq -e '[.checks[] | select(.ok == false)] | length == 0'The shapes those depend on are pinned by a test, so a script built on them does not break quietly:
agent list .agents[].name .agents[].cli .agents[].status .agents[].role
inbox .waiting[].to .waiting[].unread .waiting[].oldest
.waiting[].from
run .result.runId .result.result .result.isError .result.costUsd
ask .result.answered .result.answer .result.chain
runs .runs[].id .runs[].cause .runs[].costUsd
delegations .delegations[].from .delegations[].to .delegations[].task
.delegations[].blockedBy.body
seats .seats[].cli .seats[].used .seats[].hasCredential
.seats[].installed
spend .costUsd .byAgent[].agent .byCause[].cause
jobs .jobs[].kind .jobs[].state .jobs[].subject .jobs[].agent
.jobs[].progress.step
doctor .tenant .checks[].name .checks[].ok .checks[].detail
ship .result.outcome .result.reason .result.steps .result.costUsdExit codes still apply with --json: a bounced moa send exits 2 and prints
the bounce.
Common flags
--watch print what the agent does, as it does it
--detach start the work and return at once
--json machine-readable output only
--profile <name> a saved workspace other than the current one
--timeout <s> stop waiting; the work carries onTab completion: eval "$(moa completion zsh)" (or bash) in your shell's rc file.
Where it keeps things
~/.moa/config.json saved workspaces and their tokens (mode 0600)
<folder>/secrets.env, .env the deploy folder's secrets (mode 0600, gitignored)
<folder>/.moa/state.json what moa deploy made there, for moa down
MOA_HOME use another folder than ~/.moa
MOA_URL, MOA_TOKEN talk to this workspace, ignoring the saved ones (CI)Licence
fnMoa is proprietary: Copyright (c) 2026 fnLog0, all rights reserved. See
LICENSE in this package. Using it needs a licence from
https://fnmoa.locusgraph.com:
- Free: one workspace on This machine.
- Team and Business: more workspaces, on Vercel and Cloudflare too. Coming soon.
The workspace checks its licence once a day. If the plan lapses, there is a grace period. After that the workspace is read-only until the plan is paid again, and no data is deleted.
Versions 0.1.0 to 0.3.0 of this package are an older, unrelated CLI and are deprecated.
