oneie
v5.5.2
Published
ONE agent CLI
Readme
@oneie/cli
The command line for ONE — scaffold, validate, publish, and operate AI agents, skills, and whole agency workspaces from your terminal.
Ships two bins: one and oneie (aliases — same entry).
npm install -g @oneie/cli
# or run without installing:
npx @oneie/cli --helpQuick start — operate an agency on ONE
A ONE node (agency or client) is three folders the CLI compiles to the substrate:
<node>/
├── ai/ agents · skills · tools · workflows · context.md
├── data/ types · lifecycles · content
└── _node.toml who it isone setup # stand up your agency workspace (idempotent)
one client add <slug> # provision a managed client (workspace + credits + brand + agents)
one push [clients/<slug>] # compile ai/ + data/ → agents, skills, context, lifecycles, types, workflows
one status # P&L — funded · burned · margin · balance
one deploy # wrangler deploy the node's site/
one whoami # who am I, which workspace — proves the key is live
one doctor # config / key / reachability check — exit 0/1 for CI
one ship "<message>" # git add + commit + push in one stepone push understands TOML array-of-tables ([[stages]], [[fields]]), compiles
data/lifecycles/*.toml and data/types/*.toml into the shapes the substrate expects, and
deploys ai/workflows/*.md as runnable workflows — the step graph rides in a fenced
```graph block ({ "steps": [...], "edges": [...] }), prose above it is human docs.
Say it — oneie message
One command, no receiver names. The router picks who answers, the answer prints here, and a receipt says what it cost:
npx oneie message "add a dark-mode toggle"
npx oneie "add a dark-mode toggle" # bare text works too: anything that is not a command or a flag
oneie message "…" --json # the whole reply as JSON (answer, model, shape, usage)The text goes to the router (world:route), which derives its tags on the server, picks a model, and returns the reply. The CLI waits up to 30 s. The receipt under the answer shows the model and shape (warmed from measured runs, cold if unmeasured), tokens in and out, and a cost estimated from those tokens at the registry price. The billed figure arrives later with the hourly gateway mirror, so it prints as pending with a link to the stream (the link shows <your-group> as a placeholder — use your own slug). Any figure the CLI could not read prints as unmeasured, never as 0, and it never prints a "Nx cheaper" multiple.
Exit 0 with an answer; 1 when there is no key (it prints not signed in — run: npx oneie login), the call is refused, times out or comes back empty; 2 for empty text. Under --json it exits 0 only when the reply carries text.
Reach the whole surface
The receiver namespace is the API — discover it, then call anything:
one catalog [--goal G] # browse receivers grouped by recipe
one ask <receiver> '{json}' # typed, validated pass-through to any receiver
one signal <receiver> '{json}' # fire-and-forget variant
one groups | actors | things | paths | events | learning # dimension reads
one mark | warn | fade | follow | select # the verbsBuild agents & skills
one agent new <name> --profile core|commerce|asi
one agent validate | lint | compile --target mcp|a2a|web|skillmd
one agent publish # sync to your ONE workspace
one agent eval # run frontmatter evals (also: one skill eval)
one skill new | import <ref> | publish
one init --name N --email E # zero-to-live: register + workspace + agent + skills
one dev # boot the local CF stack: build → D1 migrate → wrangler dev
one connect telegram --token T # connect a channel by pasting its credential
one trail <tag,tag> # ranked next-best tasks for a tag combinationTasks — see, sequence, act
one tasks brief --workspace one --tree # one screen: counts, ready, blocked-by, stale DONE, next five moves
one tasks board --workspace one --tree --all # every row; --kind work|approval|all (default work)
one tasks plan <goal-tid|tag> --tree # waves + critical path in hops; cycles, outside waits, ownerless
one tasks plan <goal> --emit dispatch --out w.json # hand-off: tid/assignee/model/effort/touches/accept per wave
one tasks plan <goal> --draw # the plan DRAWN; in a terminal it is live: select, act, see what changed
one tasks plan <goal> --draw --print # the same picture once, for an agent or a pipe (default with no TTY)
one tasks sequence <tid> <tid> <tid> --apply # chain in order (one tasks:bulk call); preview without --apply
one tasks depend <tid> <blockedBy> # one edge; undepend removes it
one tasks set --where-tag t --set-status done # filter-driven edit; preview unless --apply
one tasks bulk --file plan.json # creates/edits from a file, chunked at 25--draw: see the plan, act on a row, see what happened. Waves are columns and the critical
path (the chain that decides when the plan finishes) is drawn as arrows along the top. Every other
dependency is written in words ("waits Build it"). A parent with open subtasks is never shown ready,
because tasks:claim would refuse it. In a terminal: arrows select a row, then c claim · d done ·
o reopen · m comment · enter notes · r re-read · q quit.
After each action the bottom line shows the receipt, and what just became ready and whether the
critical path moved are highlighted. The change is applied from the action's receipt at once. The
board snapshot is a ≤60s memo, and forcing a rebuild (fresh) re-reads every row in the group, which
has crashed the worker, so the view never forces one; it says the snapshot is catching up. A refusal
reads ✗ … refused: <reason> (nothing changed). --save <name> also writes the drawing for
one draw <name>.
Rows tagged approval are parked workflow runs: excluded by default, counted, and never closed by
status (set --apply refuses them unless --kind all). Every read states total, truncated and
asOf; a partial read says it is partial.
Auth
one login # browser device flow (RFC 8628) → human-class key
one login --key <key> # or paste a key directly
# either way: stored at ~/.config/oneie/key (0600)
# or set ONEIE_API_KEY in the environmentone login with no --key prints a short code, opens https://one.ie/device, and waits while you sign in and Approve. The key it stores is human-class — bound to your own slug, revocable at Profile › API keys.
When the class matters: keys minted by one init/onboard/setup are agent-class and are categorically blocked from the human-management receivers (manage_actors, manage_lifecycle, manage_members, …) — the error is forbidden with callerRole: "agent". If you hit that wall, one login is the fix. Full model: text/api-keys-docs.md § Two key classes.
Workspace operations use a Bearer token <slug>:<key>. Override the API base per command with --api (default https://api.one.ie, the gateway).
Join an existing group
one login # become someone — no new group
one request acme --reason "I run your newsletter" # ask the owner; prints the requestId
one requests # your asks
one requests --group acme --status pending # an owner's queue
one approve <requestId> # owner answers (--reject to refuse)Bare one prints this door beside the new-group one and never prompts. approve needs manage_members, so an owner answers with a one login key — an agent-class key is refused. Every verb takes --json and --api; with no key it exits 1 pointing at one login.
Your key — one key
A key is 24 words you write on paper. ONE never holds it and cannot recover it. Every verb here runs on your machine; only verify calls the network, and it cannot send the key.
one key new # mint 24 words, printed ONCE, for paper (v2: standard BIP-44 paths — what one.ie mints)
one key new --derivation v1 # the original ONE derivation (HKDF labels)
one key recover # rebuild from your words — prints BOTH address sets
one key public # the public identity + four chain addresses; both sets unless you name --derivation
one key verify # compare this key's public identity with the server: locked | verified | mismatch | unregisteredThe words are never accepted on the command line (shell history, ps) — pipe them on stdin, set ONE_WALLET_MNEMONIC, or type them at the hidden prompt. --phrase and --mnemonic are refused, and a stray argument is never echoed back, because it may be your phrase. The words print only to a terminal: a shell that looks like an agent or CI is refused, and a non-terminal stdout needs --yes-print-to-pipe (a printer, on purpose). Under --json the words never reach stdout; --out <file> writes them at mode 0600 and needs --yes-i-accept-plaintext-file. That is detection, not proof — a pty with no agent marker is not caught. A key minted under v1 and one minted under v2 have different addresses from the same words, which is why recover and public print both.
Wallets
Every actor — human or agent — gets real, self-custodied multi-chain wallets. The CLI reads and writes the same wallet:* receivers /w uses:
one wallet create --workspace <slug> --kind agent # provision a wallet row (idempotent)
one wallet get --workspace <slug> # credits, ceiling, live balances, delegated wallets
one wallet register --from keys.json --workspace <slug> # register the PUBLIC receiving addresses — first write wins, permanent
one wallet transactions --workspace <slug> --limit 50 # unified feed across every settlement rail
one wallet faucet <address> --chain sui # fund from testnet — sui/sol auto-fund, others return a manual URL
one wallet send <to> --chain SUI --amount 5 --payment-tx <hash> --workspace <slug>
one wallet keygen # mint a self-hosted 4-chain wallet locally (a fresh one is v2), offline
one wallet paper # reprint the 24 words for paper — terminal only, same refusals as `one key new`register reads a wallet keygen output but transmits only its four address fields — the map it sends is built key-by-key from a fixed chain whitelist, so a mnemonic or privateKey in that file has no route onto the wire. The write is first-write-wins per chain and cannot be corrected afterwards (address_conflict), so it prints the addresses and asks for confirmation; --yes skips the prompt for scripted provisioning (needed when the JSON arrives on stdin, which leaves no terminal to answer with).
send never signs — sign and broadcast the transfer yourself first (agent-wallet-client.ts for an agent, /w for a human), then pass the resulting transaction hash. The private key never crosses this command. faucet talks to pay.one.ie directly (its own protocol, not the /ask gateway) and needs no credential — funding any address is public by design, funding ≠ ownership.
Operate the business
one trade market | hire | bounty | earn | usage # marketplace: browse, hire peer agents, post bounties, check metering
one workflow list --slug S # list workflows for a workspace
one workflow pull <id> --slug S [--out D] # round-trip a workflow's step graph to a local file
one workflow push <id> <file> --slug S [--commit] # apply a diff — simulates first, --commit persists
one workflow run <id> --slug S # start a run
one plugin list | add <name> | buy <name> | grant <name> --to <slug> # browse, install, buy, or resell a plugin
one staff invite [email] | add-agent <name> # invite a human or create an agent staff member
one links create <actorId> --to <path> # mint a personalised, actor-bound tracked link
one broadcast list | send <id> # blasts to a workspace audience
one segment list | preview <id> # audience segments — live count + sample
one newsletter pull <id> | push <id> <file> # round-trip newsletter content as a local file
one social list --group G | create --group G ... # draft/publish/list social posts
one fn run <name> '{json}' | list # call an allowlisted analytics/routing RPC
one coordinate recall [about] | reputation [uid] # cross-session memory and reputation
one group bulk-create --csv # idempotent batch client provisioning
one shop init [--facts F] [--workspace S] # seed the twelve-page shop pack; a fact left out stays a VISIBLE placeholderYour own endpoints — a tenant's HTTPS, called by a workflow
Register an HTTPS endpoint you own and let a workflow tool step call it. The
URL is SSRF-checked when you register it and again on every call — the
check is not a one-time gate at the door.
one endpoint add <name> <url> [--method POST] [--returns json|url|bytes]
[--auth-header Authorization] [--secret-env VAR] # the secret is read from the ENV, never from argv, which the shell records
[--timeout 20000] [--max-bytes 5242880] # clamped 1000-120000 and to 10485760
one endpoint list [--workspace S] # the sealed secret has NO read door — the column says yes or no, never the value
one endpoint test <name> # run the SSRF guard WITHOUT spending a request; the refusal comes back by name
one endpoint call <name> --input '{json}' # the same door a workflow `tool` step uses--workspace on any of these is a workspace you must already control: one you
may not write is refused, never silently downgraded to your own.
An endpoint is reachable two ways, and the second one is why an outside
agent can join at all. A workflow tool step names it —
{ receiver: "endpoint:call", args: { endpoint: "<name>", input: {…} } } — and
takes any request and response shape you like; in bytes or url mode the
result is filed in the media library and you get the reader URL back. And an
agent whose frontmatter says model: endpoint/<name> is served by your
service: it gets an actor id, a chat UI renders it, and agent:run invokes it,
with none of your code running on anyone's machine. That second path needs your
service to speak OpenAI chat completions, because that is the shape every
other provider branch already constructs; a service with its own shape belongs
on the first path, which cares about neither.
endpoint/<name>/<model> overrides the model id sent in the request body. A
handle registered by another workspace is not found, never borrowed, and an
endpoint that cannot be served refuses rather than quietly answering from a
different model.
Media — the library an agent can reach
one media list [--workspace S] [--kind image|video] [--prefix media|chat|ads|products|pages|brand|courses]
one media list --all # follow nextCursor to the end (ceiling 20 pages)
one media upload <path> [--workspace S] [--filename N] # a local file, sized against the 5 MiB cap before it is sentTwo verbs, because only two earn a wrapper over one ask. list renders
media:list's honest fields — --limit is R2 objects scanned per page, not
rows returned, and a total printed beside a truncation notice is a floor, so
the renderer says "at least N" rather than reading as complete. upload exists
because you cannot put a local file in a JSON body by hand; it reads, sizes and
encodes it, and the printed key names the workspace prefix the bytes actually
reached — media:upload refuses a workspace you may not write instead of
silently downgrading to your own.
Generation is deliberately not a subcommand — it is a prompt string in a JSON body, so the raw door is no worse than a wrapper:
one ask media:generate --data '{"prompt":"a red bicycle"}'Draw — ASCII diagrams, onto the board
one draw [name|path] # open lazydraw (the TUI) on a drawing
one draw list # saved drawings
one draw export <name> [--basic] [--fenced] [-o out.txt]
one draw attach <name> --task <tid> [--workspace S] # post it to the task's threadone draw runs lazydraw (MIT, Çağrı
Gökpunar). It is a Bun app, so it is spawned rather than bundled: lazydraw if
it is on your PATH, else bun x [email protected]. Without Bun you get one line
telling you how to install it. Everything except attach is passed straight
through. attach exports plain ASCII in a Markdown fence and posts it through
tasks:comment, so the sketch sits on the row it explains. A diagram over the
door's 4000-byte comment cap is refused with both numbers, never cut, and a
comment the door trimmed is reported as truncated.
Chat — fan in · fan out
Send to a space, broadcast to all, or stream incoming messages:
one chat "deploy shipped" # broadcast to vespio · elitemoversca · world
one chat send --to vespio "hey donal, PR is ready" # targeted
one chat broadcast "all green" --spaces vespio,elitemoversca # explicit fan-out
one chat listen vespio # stream incoming SSE (Ctrl+C to stop)Every chat send routes through space:post — authenticated, mirrored to one.ie/u/<space>/in, delivered to every connected SSE listener.
Links
- Platform: https://one.ie
- Developers + SDK + MCP: https://github.com/one-ie/one
MIT © ONE
