Tools
Terminals and runtimes
Shell commands, shells that stay open, programs that talk back, and the runtimes and packages a build needs, set up in Mori's own folder.
What this family is for
Most builds end in a command: install, build, test, run. These tools run those commands, keep shells open across turns, talk to programs that wait for input, and make sure the runtime or library the job needs is really there. How the shared terminal works in detail is on Terminals.
Picking the right way to run something
- A quick command whose output she needs now:
run_terminal_command. It waits up to 300 seconds by default, 600 at most. - A long build or test suite:
run_background_command. It returns at once, and when the job ends Mori is told its exit code, time and last lines, so she keeps working instead of polling. - Work that needs state:
terminal_openandterminal_run, a named shell wherecd, variables and running programs persist. It is the same shell you see in the Terminal tab. - A program that asks questions:
cli_startandcli_send, for a REPL, a database shell, ssh or another coding agent. These are not available on Windows. - A server that never exits:
start_dev_server, never a plain command. - Many steps over files, data or links in one go:
run_script, a JavaScript program with a small bridge to files, search, web requests and the shell. It is capped at five minutes and 50 calls, and it cannot send messages, buy anything or touch memory.
Output stays readable
A chatty command cannot flood the conversation. Output is kept to the start and the end, 50,000 characters, and the full text is saved to a file she can read when she needs the middle. cli_send reports only what the program printed, never what it might have said.
Runtimes and toolchains in her own folder
A missing tool is never a reason to fail the task. runtime_status checks what is installed, and runtime_setup puts Node, Python, git, ffmpeg or Ollama in Mori's own folder: each download pinned to an exact version and checked against its published checksum, with no admin password and nothing changed outside that folder. toolchain does the same for compilers and game toolchains and builds a project in any of the common build systems. discover_clis scans for the command line tools already installed, so she reaches for ones that exist.
An install for the whole computer, through Homebrew or winget, runs only when you asked for it in your own words.
New dependencies are judged first
resource is how a new library gets into a project. It finds candidates for a need written in plain words, then judges one: its licence, upkeep, known vulnerabilities at that exact version and whether it runs install scripts. It installs into the project only, pinned, with the project's own package manager, and holds install scripts back unless they are allowed. It also lists what it installed and takes one out again.
The rails on every command
Every command, whichever tool sends it, meets the same rails. Commands that can wreck a computer are refused: deleting your home folder or a protected root, formatting disks, fork bombs, shutting down, killing Mori, piping a download into a shell. A command the guard cannot read is refused too. Typing into a terminal window through the keyboard tools counts as running a command and meets the same guard.
There are no approval cards unless you turn asking on. See Asking and the safety rails.
Permissions and limits
Almost all of this family follows the Terminal Commands switch in Settings > Permissions, and run_python and run_script count as commands too. At the Safe level none of it runs. list_processes and kill_process find and stop a stuck program by its process id. A command started by a turn ends when you press Stop.
Each description below is the exact text her model reads when it decides which tool to call.
Run a shell command; returns output and exit code. The folder persists between calls. Timeout 300s (max 600); past it, it moves to a background job, not killed. For servers or watchers that never exit, use start_dev_server instead, never run them here. Run the test or build command on its own line. A line that also names package.json or .npmrc makes Mori ask.
Start a command that finishes on its own (a test suite, a build, a long script) in the BACKGROUND and keep working: it returns a job id at once and you are told automatically when it ends (exit code, duration, last output, the full log's path), so do not poll for it and do not re-run it meanwhile. list: true prints this session's jobs, read: <id> its output, kill: <id> stops it. A server or watcher that never exits is start_dev_server; a quick command whose output you need now is run_terminal_command.
Open a NAMED, persistent shell session that survives across turns (cd, env vars and running processes all persist). Reuse the same name to keep working in it. Backend 'local' = the user's own shell; 'docker' = a hardened throwaway container (needs Docker); 'ssh' = a remote host (needs key auth). The user's MoriTerminal UI shares these exact sessions with you.
Run a command in a named session (opened via terminal_open) and wait for it to finish. Returns the exit code and bounded output (head+tail). State persists between calls in the same session. For servers that never exit, prefer start_dev_server.
Read the most recent output lines from a named session without running anything, to check on a long-running command or catch up on a session the user has been typing into.
List all open terminal sessions as JSON: name, backend, working directory, whether a command is running (busy), and when it was created.
Ctrl-C a terminal session: stop whatever is running and make the session usable again, KEEPING its scrollback and working directory. Use it when a command is stuck, is taking far too long, or turned out to be interactive (a REPL, ssh, a pager). Prefer it over terminal_close, which throws the whole session away.
Close a named terminal session and reap its shell (for docker/ssh, the container or connection). Always close sessions you no longer need.
Start an INTERACTIVE command-line program and talk to it. Use this, NOT terminal_run, for anything that prints a prompt and waits: another AI coding agent (claude, gemini, codex, aider), a REPL (python3 -i, node, irb), a database shell (psql, sqlite3, mysql), ssh, or any wizard that asks questions. It runs under a real pseudo-terminal. Returns {ok, session, state, output}: 'idle' means it has finished printing and is WAITING FOR YOU, so reply with cli_send; 'exited' means it ended. The session persists across turns, so variables and logins survive. macOS/Linux only for now.
Type something into a running interactive CLI session and get back what it says: send one message, read the reply, decide what to send next. Returns JSON {ok, state, output}: 'idle' = it answered and is waiting again; 'exited' = the program ended; 'timeout' = still busy, call cli_read to keep waiting. Send control keys as literal escapes in text (e.g. \u0003 for Ctrl-C). Do NOT invent what the program said: report only what comes back in output.
Read new output from an interactive CLI session without sending anything, to keep waiting on a program still working (after cli_send returned state "timeout"), or to catch up on one running in the background. Returns only what arrived since the last read unless all=true.
List the interactive CLI sessions currently open, with what each is running and whether it is still alive.
End an interactive CLI session and kill the program. Do this when finished, sessions hold a real process open.
Run a Python 3 snippet and get its output, for data work, quick math, scripts, using Python libraries. (JavaScript orchestration → run_script; shell commands → run_terminal_command.)
Write JavaScript that orchestrates MANY steps in ONE turn: the fast path for anything multi-step over files, data or APIs. Inside, an async `mori` object bridges to the machine: await mori.readFile({path}), mori.writeFile({path,content}), mori.listDir({path}), mori.searchFiles({path,query,file_pattern}), mori.httpRequest({url,method,headers,body}), mori.runTerminal({command}), mori.readPdf({path}), mori.ocrImage({path}), mori.queryCsv({path,sql}). ONLY what you console.log() comes back. Limits: 5-min/50-call cap, console output capped at 50KB, mori.httpRequest bodies capped at 200,000 chars (the header line above the --- says when a body was cut), no secrets in env, and NO sends, purchases or memory here: those stay their own tools. Use for 'rename every file matching X', 'pull these 10 URLs and summarize'; use normal tools for single actions.
List running processes (pid, CPU, memory). Optionally filter by name.
Terminate a process by PID. Use to stop a runaway or stuck process (get the PID from list_processes).
Check which runtimes this machine has, node, npm, python, git, ollama (plus Homebrew/winget). Call this BEFORE assuming a runtime exists, and whenever a command fails with 'command not found'. A missing runtime is NEVER a reason to fail the task: report it and use runtime_setup.
Install a runtime the task needs (node, npm, python, git, ffmpeg, ollama): Mori's own checksum-pinned copy in her own folder, never system-wide (a package manager only for ollama). Asks first only where asking is on. If no package manager exists it returns the official download URL instead of piping scripts into a shell. For ollama, pass 'model' to pull a local model (e.g. 'llama3.2'), needed before local inference works. After installing, verify with runtime_status.
The compilers, checkers and runtimes for game and native code on this computer (C and C++, Lua and Luau for Roblox, FiveM, LOVE or WoW, Java for Minecraft mods, C#, Rust, Godot). action "status" lists what is here for a language; "setup" sets one up by the safest door: Mori's own copy in her own folder where there is one; an install for the whole computer (Homebrew, winget) only when the person asked for it in their own words, or says yes on a card while asking is on. Never install a toolchain any other way. "inspect" reads a binary (exe, dll, so, dylib, class) and says if it is 32 or 64-bit, its architecture and C++ runtime. Every edit of a Lua, Luau, C, C++ or Java file is already checked after the write; when that result says "not checked", setup is how to fix it. Security scanners (gitleaks, osv-scanner, trivy) set up the same way, pinned. "build" (path: a project folder) detects its build in any language (CMake, make, Cargo, Go, Gradle, Maven, Swift, .NET, Zig, Dart, Flutter, Composer, Bundler, Mix, Cabal, Stack, LuaRocks, npm, Python), sets up what is missing in Mori's own folder, installs dependencies inside the project, builds, tests and reads the errors back. Emulators: "testroms" (name chip8, nes or gb; path the project) fetches the public test ROMs into tests/roms, checksum-pinned; "emucheck" compares a run: name nestest (path the CPU trace), blargg (path its printed output), or chip8, nes or gb (path a frame dump .ppm/.pbm; chip8 with rom is compared to Mori's reference).
Find, judge and install what a build needs from the official registries (npm, PyPI, crates.io, GitHub): libraries, starter repos, templates, plugins. find: a need in plain words gives a ranked, vetted shortlist. judge: licence, upkeep, use, known vulnerabilities at the exact version, install scripts, the version to pin. docs: its README. install: into the project only, pinned, with the project's own package manager; install scripts are held back unless allow_scripts. list: what Mori installed here and from where. remove: takes one out again. Use this instead of typing install commands for new dependencies.
Run npm install in a project directory
Scan this machine for installed command-line tools (git, gh, docker, npm/pnpm/bun, python/uv, aws, gcloud, ffmpeg, imagemagick, psql, jq, terraform, kubectl and more) and remember the result for the rest of the session. Run once near the start of terminal-heavy work so you reach for tools that actually exist.
Set up and change things in web services as checked routines, the way a careful engineer does: op "resend" (sending email for a domain: adds the domain, writes the SPF, DKIM, MX and DMARC records at the DNS provider, verifies, makes a sending-only key), "domain" (Namecheap: is a name free, its exact price, and buying it only after the person's yes), "dns" (records into a Namecheap or Cloudflare zone, merging SPF, never doubling DMARC, keeping every existing record, then checking public resolvers), "bot" (platform telegram, discord or slack: the token into the key store, checked against the service, the invite link), "vercel" (deploy a folder, optional custom domain), "vercel-domain" (point a domain whose DNS is at Namecheap or Cloudflare at a Vercel project: the apex A and www CNAME replace the old ones, mail and verification records kept, checked at public resolvers and by Vercel), "vercel-env" (environment variables from the key store, the value on stdin, each checked), "pages" (Cloudflare Pages deploy), "github" (repo, Actions secrets from the key store, a workflow run). action "status" lists the service CLIs (installed, signed in) and the key store's key NAMES; "install" (name: gh, vercel, wrangler, netlify, supabase, stripe, doctl) puts an official CLI in Mori's own folder; "key" (name, e.g. resend.apiKey) opens Mori's key box for the person to paste a key a service showed them: it goes straight to the key store, so NEVER ask them to paste a key into the chat. It returns at once and the box waits for them: run the op again after they say it is saved. Run the same op again to resume where it waited. Sign-in, 2FA, CAPTCHA and card details are always the person's: the routine stops and says what they do. LAYER ORDER for any service work: this tool or the service's official CLI (run_terminal_command, signed in with the CLI's own browser login via cli_start) first; the person's own Chrome through the Mori extension (chrome_tabs open, chrome_read_page, chrome_act) for pages with no API, like a sign-up; the screen (use_computer) last. Stripe is read-only unless the person asks for a change.