@neuralis/cli
v0.3.1
Published
The neuralis command line: sign in to a Neuralis host, call its package routes as yourself, and launch your agent client with your Neuralis capabilities
Maintainers
Readme
@neuralis/cli
neuralis is how a person — or the agent client on their own machine — reaches a Neuralis
workspace as themselves: one host, one user, one project, one agent, and every call checked on the
server against exactly that identity. The same neuralis request line also runs inside an
activated skill's shell on the platform, so a skill's call lines work in both places. It is a thin
client: the host stays the only authority over who may do what.
Public guide: https://docs.neuralisapp.com/getting-started/cli.
Install
npm install -g @neuralis/cli # Node.js >= 22A tsup ESM bundle (dist/neuralis.mjs, the npm bin) with no host, Next.js, tsx or node-pty
runtime dependency. The platform image installs the same artifact root-owned under
/usr/local/lib/neuralis-cli with /usr/local/bin/neuralis on the PATH. Inside a host install
folder the fifteen operator verbs (setup, rebuild, upgrade, …) are forwarded to that
install's own host bin (commands/ops.ts), with the original cwd as INIT_CWD.
Commands
| Verb | What it does |
|---|---|
| login --host <url> [--project] [--agent] [--auth-server] [--no-browser] | Two PKCE consents on the host's authorization server: one for the package-route API (<app origin>/api, routes:call), one for MCP. Discovery follows RFC 9728 from the catch-all's 401 challenge |
| logout | RFC 7009 revoke of both refresh grants, then the local config is removed; an access token already issued lives until it expires (≤ 1 h) |
| whoami | GET @neuralis/agent-core/cli/session — the identity the server sees |
| request <METHOD> <package>/<route> [flags] | One package-route call (contract below) |
| skill list · skill read <id> [--package] | The agent's active skills from files/catalog; read prints one SKILL.md from files/bundle byte for byte |
| mcp | stdio ↔ streamable-HTTP bridge to the host's MCP endpoint with the MCP grant (src/mcp/bridge.ts): stdout carries protocol only, a request lost to a dropped connection is answered with an error, never replayed |
| plugin export --for claude\|codex\|cursor\|agents --out <dir> | files/bundle written as a plugin (src/plugin/export.ts): consumer layout, deterministic name collisions, provenance in a separate JSON, sibling temp + rename, an existing target refused |
| launch claude\|codex [--project] [--agent] [--conversation \| --new] [--layer-mode merged\|governed\|native] [--native-root] [-- <client args>] | Starts the client with a content-addressed capability set (below) |
| device · up · down · tunnel · launch --profile | Listed as planned; using one is refused, never a no-op |
The request contract
<package>/<route>is the path after/api/packages/(a scoped package is two segments,@neuralis/admin/audit); a URL, a scheme,//,./..(percent-encoded too),\or a#fragment is refused before anything is sent (exit 2). The?querypart passes as written.-f k=va string field (GET/HEAD: query, else a JSON body key;k[]=vappends) ·-F k=vtyped (true/false/null/integers;k=@file/k=@-the content as a STRING) ·--input file|-the raw body ·--form k=v|k=@filemultipart. The three body forms are mutually exclusive; a mutation with none sends no body and no Content-Type.-Hadds a header but neverAuthorization/Cookie/Host.- 2xx (202, 207 included): the body on stdout byte for byte, exit 0. Non-2xx:
HTTP <code>and the body on STDERR, stdout empty, exit 1;--jq(jq 1.8 as WebAssembly, loaded only when used; noinclude/-L) runs only on a 2xx JSON answer.-iprints the status line and headers first. - Exit codes:
0ok ·1HTTP error/other ·2usage (nothing sent) ·3no credential, a refused renewal or a half ticket pair ·4transport or a redirect (never followed) ·28the client deadline (--max-time, defaultNEURALIS_SHELL_TIMEOUT_MS − 10 s, min 5 s, else 600 s). - A mutation is never retried; the one replay is an OAuth GET/HEAD
401→ one forced refresh. plugin exportandlaunchfollow the same table: a401the renewal could not cure is3, a permission denial (403) is1. A reader that closes the pipe early (| head) ends the run with0.
The CLI is an agent-class caller (interactive:false): routes gated by requireHumanCaller (provider
account connect, secret writes) answer 403 human_only — by design, never a CLI bug.
Credentials
- Ticket mode —
NEURALIS_SESSION_TOKEN+NEURALIS_APIboth present (an activated skill's shell): the ticket goes only to thatNEURALIS_APIorigin, nothing is read or written on disk, and a present ticket never falls back to a login. One of the two alone is refused with the activation hint.SKILL_NAMEbecomesX-Neuralis-Skill. - OAuth mode — the config under
NEURALIS_CLI_HOME, else$XDG_CONFIG_HOME/neuralis/~/.config/neuralis(macOS~/Library/Application Support/neuralis, Windows%APPDATA%\neuralis) — deliberately NOT the host data home, whichneuralis backupcopies: a0700directory,0600files, atomic renames, one sign-in per host/project/agent (--host/--project/--agentorNEURALIS_HOST/PROJECT/AGENTchoose it). A stored token is sent only to the origin it was issued by. Renewal happens on use; a file lock lets several processes share one renewal (bounded wait, stale-lock recovery, no daemon), and a later process reads the renewed grant from the file.
launch
GET agent-core/cli/launch-context (the packages overview and runtime summary in their external
form — no rule/instruction text, no container paths) plus files/bundle become one set under
~/.neuralis/launch/<hostKey>/<projectId>/<agentId>/sets/<setHash>/: plugin/ (the skills, the
bundled neuralis skill and the consumer manifest, no .mcp.json), mcp.json (the neuralis mcp
bridge as server neuralis_launch, no token), system-append.md and set.json. Unlike the sign-in config, launch sets live under the host data
home on purpose (the owner's chosen layout): they hold no secret, neuralis backup copies them
along, and any launch regenerates them. An unchanged set
is reused with zero writes; a conversation pointer (conversations/<key>.json) records which set a
conversation used; --new creates a Neuralis conversation for the agent and launches on it (its id
is printed for a later --conversation). A running client holds .lock.<pid>; GC at every launch removes sets no live
lock or pointer holds (a held set is never moved aside or deleted) and pointers whose conversation the server no longer reports (no timer, no
daemon; --gc runs it alone). Modes: merged (the client's own setup plus the set), governed
(only the set — the client's own deny rules and hooks are not loaded; Claude Code only), native
(the client unchanged). Codex receives the summary and the skill pointer through
developer_instructions and the bridge through -c mcp_servers.neuralis_launch.*; forwarded
client flags that would replace the layer are refused by name (src/launch/{claude,codex}.ts) —
like every argument-only refusal, before any request or write.
Layout
| Path | Role |
|---|---|
| src/index.ts, src/registry.ts | Entry and the static verb registry (EXIT table, the one error → exit-code boundary) |
| src/client.ts | Route parsing, the HTTP send, ticket/OAuth credential resolution, the default deadline |
| src/auth.ts, src/config.ts | The two PKCE grants, refresh with the file lock, revoke; the config files |
| src/commands/* | One module per verb group |
| src/plugin/export.ts, src/launch/*, src/mcp/bridge.ts | Plugin writer, launch set/GC/consumer argv, the MCP bridge |
| skills/neuralis/SKILL.md, .claude-plugin/, .codex-plugin/, .mcp.json | The bootstrap skill and the static plugin manifests shipped in the npm package. The one .mcp.json runs neuralis mcp from PATH, because Codex does not expand ${CLAUDE_PLUGIN_ROOT} and Claude Code ignores cwd. The plugin therefore needs the globally installed CLI, which neuralis login needs anyway |
| test/ | Offline unit tests (injected CliContext); CI runs them on Node 22 and 26 |
License: FSL-1.1-ALv2 (LICENSE.md).
