nonoka-cli
OpenCode backend for the Nonoka Agent framework.
nonoka-cli runs as a stdio NDJSON bridge server (nonoka-cli --server) that
talks to the nonoka-opencode-provider TypeScript package. When used inside
OpenCode, Nonoka acts as the conversation/decision backend while OpenCode owns
tool execution and human-in-the-loop (HITL) approval using its native tools.
Quick install
The easiest way to get nonoka + OpenCode is the one-line installer:
curl -fsSL https://nonoka.dev/install.sh | bash
The installer will:
- Check Python 3.10+ and Node/npm.
- Install or update OpenCode.
- Install
nonoka-cliand the OpenCode provider. - Generate
~/.config/nonoka/config.yamland~/.config/opencode/opencode.json.
After installing, configure your API key and run nonoka:
# Interactive: it will ask for your key and save it to ~/.config/nonoka/.env
nonoka-cli config init
# Or set it manually
export DEEPSEEK_API_KEY=<your-key>
nonoka-cli doctor
nonoka
nonoka is a shortcut for nonoka-cli run and starts the OpenCode TUI with the
Nonoka backend. You can also use nonoka run --message "<task>" for one-shot
CLI usage.
nonoka-cli automatically loads ~/.config/nonoka/.env and ./.env on startup,
so you don't need to export every time if you save the key in .env.
To use
uvinstead ofpip, or to run non-interactively, pass flags:curl -fsSL https://nonoka.dev/install.sh | bash -s -- --uv --yes.
Manual installation
# Install nonoka-cli
pip install nonoka-cli
# or with uv
uv pip install nonoka-cli
# Install the OpenCode provider globally so OpenCode can load it
npm install -g nonoka-opencode-provider
Quick start
- Create your nonoka config (it will ask for your API key and save it to
~/.config/nonoka/.env):
nonoka-cli config init
For scripted setups, use the non-interactive mode (you'll still need to set the
API key via .env or export):
nonoka-cli config init --yes --model deepseek-chat
- Generate an OpenCode config in the current project or globally:
# Project-level
nonoka-cli opencode init
# User-level
nonoka-cli opencode init --global
- Make sure your model API key is exported, then run:
nonoka
nonoka-cli doctor
Diagnose your installation and configuration:
nonoka-cli doctor
Example output:
nonoka-cli doctor
✓ nonoka-cli 0.2.5
✓ Python 3.11
✓ opencode 1.18.2
✓ provider nonoka-opencode-provider@0.2.12
✓ config ~/.config/nonoka/config.yaml
✓ API key DEEPSEEK_API_KEY set
✓ OpenCode provider config in /home/user/.config/opencode/opencode.json
If anything is wrong, doctor prints a remedy line. To also verify the LLM API
key with a real (small) call, use:
nonoka-cli doctor --check-llm
Configuration
nonoka-cli config init
Interactive wizard that writes ~/.config/nonoka/config.yaml. It asks for a
model identifier (e.g. deepseek-chat, openai/gpt-4o, ollama/llama3.3), a
masked API key, and whether to save it to ~/.config/nonoka/.env (recommended),
directly in config.yaml, or skip saving. It also asks for a system prompt and
whether to auto-approve all tool calls.
Non-interactive example:
nonoka-cli config init --yes --model openai/gpt-4o
nonoka-cli config set <key> <value>
Update a single config value. Dotted keys are supported:
nonoka-cli config set model openai/gpt-4o
nonoka-cli config set cli.theme light
nonoka-cli config set hitl.dangerous_tools '["write_file", "execute_command"]'
nonoka-cli config show
Print the resolved configuration and its file path.
nonoka-cli opencode init
Generate or merge an opencode.json in the current directory and create
.opencode/agents/build.md from your nonoka system_prompt. The generated
config points OpenCode at the nonoka-opencode-provider package and passes the
nonoka config path to the backend.
OpenCode configuration
nonoka-cli opencode init generates two things:
opencode.jsonin the current directory, which wires OpenCode to thenonoka-opencode-providerpackage and sets HITL permissions..opencode/agents/build.md, which contains the agent prompt.
A typical generated opencode.json looks like:
{
"$schema": "https://opencode.ai/config.json",
"model": "nonoka/default",
"provider": {
"nonoka": {
"npm": "nonoka-opencode-provider",
"name": "Nonoka",
"options": {
"serverCommand": ["bash", "-c", "nonoka-cli --server 2>/tmp/nonoka-server.log"],
"cwd": ".",
"configPath": "~/.config/nonoka/config.yaml"
},
"models": {
"default": { "name": "Nonoka deepseek-chat" }
}
}
},
"permission": {
"*": "ask",
"bash": "ask",
"edit": "ask",
"write": "ask"
},
"agent": {
"build": {
"mode": "primary",
"permission": {
"*": "ask",
"bash": "ask",
"edit": "ask",
"write": "ask"
}
}
},
"tools": {
"skill": false
}
}
The "tools": {"skill": false} line disables OpenCode's native skill:<name>
tool so it does not collide with nonoka's load_skill / skill__<name>__<tool>
workflow. nonoka-cli opencode init writes this automatically.
Prompt ownership
Nonoka owns the canonical system prompt via system_prompt in
~/.config/nonoka/config.yaml. When you run nonoka-cli opencode init, the
command adapts that prompt and writes it to .opencode/agents/build.md so
OpenCode uses it for its primary agent. OpenCode-specific guidelines (tool
names, approval behavior, path conventions) are appended automatically; they are
not mixed into Nonoka's core prompt, so the same config works for other
frontends in the future.
Human-in-the-loop
When running inside OpenCode, HITL is handled by OpenCode itself. The generated
opencode.json sets "*": "ask" so every tool requires approval. Because
Nonoka forwards OpenCode's native tool definitions to the model, approval
dialogs render natively for bash, read, write, and edit operations.
nonoka-cli's own cli.auto_approve and hitl.policy settings only apply to
standalone server / CLI mode. In OpenCode mode, permissions are governed by the
permission block in opencode.json. To keep nonoka.yaml as the single
source of truth, add a permissions block and re-run nonoka-cli opencode init:
permissions:
read: allow
bash: ask
write: ask
edit: ask
cli.auto_approve: true still auto-allows the core coding tools when no
permissions block is present. For standalone mode, use hitl.policy: auto.
External-tools mode
When nonoka-cli runs inside OpenCode, it operates in external-tools mode by
default. OpenCode sends its native tool list (e.g. bash, read, write,
edit, todowrite) to the provider; nonoka-cli registers them as
ExternalCapability objects and lets OpenCode execute them. This means:
- OpenCode owns tool execution, HITL approval, and TUI rendering.
- nonoka owns decision-making: which tool to call, when, and with what arguments.
- Tool results are returned by OpenCode and resumed via
Runner.resume_external_tools().
To start external-tools mode, run OpenCode with the generated opencode.json;
the provider spawns nonoka-cli --server automatically.
MCP and Skill support
nonoka-cli can merge MCP tools and lazy-loaded skills alongside OpenCode's
native tools. Configure them in ~/.config/nonoka/config.yaml:
model: deepseek-chat
mcp_servers:
filesystem:
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/docs"]
skills:
- code-review
- nextjs-best-practices
- MCP tools are executed locally by nonoka-cli and are exposed with a
mcp__<server>__<tool>namespace prefix so they do not collide with OpenCode native tools. - Skills use nonoka-agent's lazy
SkillRegistry. Only names and descriptions are injected into the system prompt; full guidance is loaded on-demand via theload_skilltool. Skill tools are prefixed withskill__<skill>__<tool>in external-tools mode.
Both MCP tools and skill tools remain available in standalone mode without any prefixing.
Skill tool import paths
A skill file lists its tools with an import entry in YAML frontmatter:
---
name: greet
description: A simple greeting skill.
tools:
- import: greet_tool:say_hello
---
When loaded, use the say_hello tool to greet the user by name.
greet_tool:say_hello is resolved by Python's normal import machinery, so the
module must be importable from the project working directory (or from a directory
on PYTHONPATH). Place the tool module next to your skill file or add the skill
source directory to PYTHONPATH if you use a nested layout.
Avoiding OpenCode's native skill tool
OpenCode has its own skill:<name> syntax that conflicts with nonoka's
skill__<name>__<tool> namespace and load_skill tool. The generated
opencode.json disables the native skill tool with "tools": {"skill": false}.
If you hand-write opencode.json, keep that setting so the model only uses
nonoka-managed skills.
Git safety net
nonoka-cli can automatically create a git checkpoint before each file change
and roll back on failure. Enable it in nonoka.yaml:
git:
auto_checkpoint: true
auto_commit: true
When enabled, the model can call git_checkpoint before dangerous operations
and git_rollback to restore the last checkpoint if something goes wrong.
Repo map
For large repositories, nonoka-cli builds a lightweight symbol index and
injects a repo map into the system prompt. This reduces blind file reads by
giving the model a structural overview of classes, functions, and exports.
Configure it in nonoka.yaml:
repo_map:
enabled: true
max_tokens: 4000
The build_repo_map and search_repo_map tools let the model refresh or
query the index on demand.
Sub-agent workflow
nonoka-cli can optionally expose two sub-agent tools that the main agent can
call for complex tasks:
plan_task— delegates planning to a dedicated planner agent and returns a numbered, file-level execution plan.review_changes— delegates final review to a dedicated reviewer agent and returns a structured review with issues, suggestions, and an approval flag.
Both are disabled by default. Enable them by setting their model in nonoka.yaml:
model: deepseek-chat
max_turns: 20
agents:
planner:
model: deepseek-chat
system_prompt: "You are a planning agent..."
reviewer:
model: deepseek-chat
system_prompt: "You are a senior code reviewer..."
max_turns at the top level controls the main executor agent. The
planner/reviewer roles each have their own max_turns inside
agents.<role>.
When enabled, these tools are injected into the main agent's tool list
alongside OpenCode's native tools. The main agent decides when to call them;
the planner/reviewer run inside their own short-lived nonoka agent invocation
and return their results as tool output.
review_changes accepts an optional files argument. When the main agent
passes file paths, the reviewer reads those files and prepends their contents
to the review context automatically:
# In the conversation the model can call:
# review_changes({
# "task": "Review the changes against the goal: add logging",
# "context": "<diff or summary>",
# "files": ["src/main.py", "src/utils.py"]
# })
Note:
plan_taskandreview_changesused to live innonoka-agent. They have been moved tononoka-cliso that sub-agent configuration (model, system prompt, max turns) is controlled by the CLI config and can use different models from the main agent.
Plugin manifest
Projects can declare their own Nonoka plugins via .nonoka/plugin.json:
{
"name": "my-plugin",
"skills": ["code-review"],
"agents": {
"planner": { "system_prompt": "..." }
},
"mcpServers": {},
"allowedTools": ["read", "edit", "bash"]
}
The manifest is merged with user-level config and converted into OpenCode's
skill/permission format when nonoka-cli opencode init runs. See
.nonoka/plugin.json.example for a full example.
Known limitations
These are current behaviors observed with OpenCode CLI 1.17.18. They are tracked
here because they affect the TUI/HITL experience but cannot be fixed inside
nonoka-cli or nonoka-opencode-provider.
- OpenCode native
skilltool conflicts with nonoka skills: the generatedopencode.jsondisables it with"tools": {"skill": false}, and the adapter prompt tells the model to use onlyload_skillandskill__<name>__<tool>. - External directory rejection crashes OpenCode: the adapter now injects the current working directory into the system prompt and instructs the model to use paths relative to it, so requests outside the workspace are rare. If one still occurs and you select Reject, OpenCode may still exit. Keep requests scoped to the current working directory, or approve if the path is safe.
-
writeis auto-approved inside the workspace: even with"*": "ask"inopencode.json, OpenCode does not show an approval dialog forwriteoperations within the workspace root.bash,read, andeditdo ask. - Code blocks render as plain indented text: OpenCode renders Python and other code as plain indented output rather than fenced code blocks with syntax highlighting. This is an OpenCode TUI rendering choice.
- Short replies leave empty vertical space: the OpenCode TUI uses a flex layout, so short assistant replies appear at the top with visible empty space above the status bar. This is normal OpenCode layout behavior.
- Model may skip tools for ambiguous requests: the adapter prompt
mitigates this, but a vague request can still cause the model to answer
directly instead of calling
read/edit. Make file/tool requests explicit.
Server logs and request traces
When running inside OpenCode, the provider spawns nonoka-cli --server as a
long-lived NDJSON bridge. Server stderr is redirected by the provider to a
per-working-directory log file so it does not pollute OpenCode's TUI:
/tmp/nonoka-server-<cwd-hash>.log
In addition, nonoka-cli --server writes a structured NDJSON trace of every
request and stream event for debugging:
/tmp/nonoka-trace/trace-YYYYMMDD.jsonl
You can override the trace directory with the NONOKA_TRACE_DIR environment
variable.
Debug environment variables
| Variable | Effect |
|---|---|
NONOKA_DEBUG=1 |
Emit debug NDJSON events from the bridge for every request and stream transition. |
NONOKA_TRACE_DIR=/path |
Directory for NDJSON request/event traces (default: /tmp/nonoka-trace). |
NONOKA_SERVER_LOG=/path |
Override the server stderr log path when running the bridge manually. |
Development
# Install in editable mode
uv pip install -e .
# Run the bridge server
nonoka-cli --server --config ./nonoka.yaml
# Lint and test
uv run --no-sync ruff check .
uv run --no-sync pytest tests/unit
Project layout
src/nonoka_cli/
├── bridge/ # NDJSON protocol, request handler, server
├── commands/ # CLI subcommands (config, doctor, opencode)
├── config/ # YAML config loading and Pydantic models
├── core/ # Orchestrator, RunnerService, SessionService, ToolService,
│ # MCPService, AgentFactory, prompt/context/task-state/output pruning,
│ # git safety net, repo map, planning, and plugin manifest
│ # agent_factory.py # Build nonoka Agent from CLI config
│ # prompt_builder.py # System prompt assembly for OpenCode mode
│ # context_trimmer.py # Turn-based context window trimming
│ # task_state.py # Local TODO state mirror
│ # tool_output_policy.py # Tool output pruning / spill policy
│ # git_service.py # Git checkpoint / rollback helpers
│ # repo_map_service.py # Symbol index generation and search
│ # planning_service.py # Planner sub-agent (AgentTool)
│ # review_service.py # Reviewer sub-agent (AgentTool)
│ # plugin_manifest.py # .nonoka/plugin.json loader
│ # plugin_manifest_converter.py # OpenCode skill/permission conversion
├── mcp/ # MCP server lifecycle manager (thin wrapper around nonoka-agent)
├── sessions/ # Session metadata persistence
├── skills/ # Skill loading shim (delegates to nonoka-agent SkillRegistry)
├── tools/ # Built-in and local tool loader
└── utils/ # Errors, logging, trace logger
packages/nonoka-opencode-provider/ # TypeScript provider for OpenCode
install.sh # One-line installer
License and attribution
nonoka-cli and nonoka-opencode-provider are released under the MIT License.
The terminal TUI and OpenCode client/server architecture are provided by OpenCode (MIT License). The agent core is provided by the Nonoka framework.
See LICENSE and NOTICE for full details.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nonoka_cli-0.2.6.tar.gz.
File metadata
- Download URL: nonoka_cli-0.2.6.tar.gz
- Upload date:
- Size: 353.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
45e1903d4c6c22a6b7cc9ee20f29064d365cdf6e654f48ede371b924a3bfb06d
|
|
| MD5 |
4598d3e0341fb9bb635715b9b3ea4f70
|
|
| BLAKE2b-256 |
fc8939c43066f0efdd50eca7bb9274dc850836e0c717d27fb2af637a9ec68ab0
|
File details
Details for the file nonoka_cli-0.2.6-py3-none-any.whl.
File metadata
- Download URL: nonoka_cli-0.2.6-py3-none-any.whl
- Upload date:
- Size: 96.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c923e1f6d5c32e8a7dad1720b4568b5f7898bc9b321df7d2829d0ad379829b99
|
|
| MD5 |
2c542cbf6aa2856e1bb0f0a41da9ce85
|
|
| BLAKE2b-256 |
77ef14bc9dd8276e520c23fe92710e1d930800d42552c9ba4ad668ee5ff43e4e
|