nonoka-cli 0.2.11__tar.gz → 0.2.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/PKG-INFO +28 -25
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/README.md +27 -24
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/pyproject.toml +1 -1
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/__init__.py +1 -1
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/cli.py +25 -15
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/doctor_cmd.py +24 -8
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/opencode_cmd.py +50 -24
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/run_cmd.py +18 -5
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/config/models.py +2 -1
- nonoka_cli-0.2.12/src/nonoka_cli/safety/__init__.py +20 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/safety/preflight.py +13 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/tools/builtins/file_tools.py +12 -2
- nonoka_cli-0.2.12/tests/unit/commands/test_cli_entrypoint.py +42 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_doctor_cmd.py +16 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_opencode_cmd.py +28 -2
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_run_cmd.py +21 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/config/test_models.py +11 -0
- nonoka_cli-0.2.12/tests/unit/safety/test_preflight.py +27 -0
- nonoka_cli-0.2.12/tests/unit/tools/test_file_tools.py +120 -0
- nonoka_cli-0.2.11/src/nonoka_cli/safety/__init__.py +0 -6
- nonoka_cli-0.2.11/tests/unit/safety/test_preflight.py +0 -14
- nonoka_cli-0.2.11/tests/unit/tools/test_file_tools.py +0 -60
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/.gitignore +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/LICENSE +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/NOTICE +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/__main__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/harbor.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/swe_bench.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/swe_environment.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/swe_runner.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/watchdog.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/events.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/handler.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/nonoka_tools.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/protocol.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/server.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/benchmark_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/config_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/eval_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/logs_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/plugin_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/sessions_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/config/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/config/loader.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/config/manager.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/agent_factory.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/context.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/context_trimmer.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/git_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/mcp_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/namespaces.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/operational_signals.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/orchestrator.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/plugin_manifest.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/plugin_manifest_converter.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/project_agents.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/prompt_builder.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/repo_map_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/run_evidence.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/runner_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/scorecard.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/semantic_cache.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/session_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/task_state.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/tool_output_policy.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/tool_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/mcp/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/mcp/manager.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/mcp/models.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/safety/sandbox.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/sessions/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/sessions/manager.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/sessions/models.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/skills/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/tools/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/tools/builtins/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/tools/builtins/agent_tools.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/tools/loader.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/utils/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/utils/errors.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/utils/logging.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/utils/trace_logger.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/conftest.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/fixtures/bridge_external_capabilities.json +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/integration/test_server_external_capabilities.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/integration/test_watchdog_process_group.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/benchmark/test_harbor.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/benchmark/test_swe_environment.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/benchmark/test_watchdog.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_events.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_handler.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_nonoka_tools.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_package.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_protocol.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_server.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_benchmark_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_config_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_eval_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_logs_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_plugin_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_swe_bench_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/config/test_loader.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_agent_factory.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_context_trimmer.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_git_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_operational_signals.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_orchestrator.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_plugin_manifest.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_plugin_manifest_converter.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_project_agents.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_prompt_builder.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_repo_map_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_safety_policy.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_scorecard.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_semantic_cache.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_task_state.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_tool_output_policy.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/sessions/test_manager.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/utils/test_logging.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/utils/test_trace_logger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nonoka-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.12
|
|
4
4
|
Summary: OpenCode backend for the Nonoka Agent framework
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -33,7 +33,7 @@ English | [简体中文](README.zh-CN.md)
|
|
|
33
33
|
|
|
34
34
|
OpenCode backend for the [Nonoka](https://pypi.org/project/nonoka/) Agent framework.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Nonoka runs as a stdio NDJSON bridge server (`python -m nonoka_cli --server`) that
|
|
37
37
|
talks to the `nonoka-opencode-provider` TypeScript package. When used inside
|
|
38
38
|
OpenCode, Nonoka acts as the conversation/decision backend while OpenCode owns
|
|
39
39
|
tool execution and human-in-the-loop (HITL) approval using its native tools.
|
|
@@ -57,18 +57,18 @@ After installing, configure your API key and run `nonoka`:
|
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
# Interactive: it will ask for your key and save it to ~/.config/nonoka/.env
|
|
60
|
-
nonoka
|
|
60
|
+
nonoka config init
|
|
61
61
|
|
|
62
62
|
# Or set it manually
|
|
63
63
|
export DEEPSEEK_API_KEY=<your-key>
|
|
64
64
|
|
|
65
|
-
nonoka
|
|
65
|
+
nonoka doctor
|
|
66
66
|
nonoka
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
`nonoka` is
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
`nonoka` is the primary command. It starts the OpenCode TUI when invoked without
|
|
70
|
+
a subcommand, while `nonoka run --message "<task>"` provides one-shot CLI usage.
|
|
71
|
+
The legacy `nonoka-cli` executable remains available for compatibility.
|
|
72
72
|
|
|
73
73
|
`nonoka-cli` automatically loads `~/.config/nonoka/.env` and `./.env` on startup,
|
|
74
74
|
so you don't need to `export` every time if you save the key in `.env`.
|
|
@@ -94,24 +94,24 @@ npm install -g nonoka-opencode-provider
|
|
|
94
94
|
`~/.config/nonoka/.env`):
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
|
-
nonoka
|
|
97
|
+
nonoka config init
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
For scripted setups, use the non-interactive mode (you'll still need to set the
|
|
101
101
|
API key via `.env` or `export`):
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
|
-
nonoka
|
|
104
|
+
nonoka config init --yes --model deepseek/deepseek-v4-pro
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
2. Generate an OpenCode config in the current project or globally:
|
|
108
108
|
|
|
109
109
|
```bash
|
|
110
110
|
# Project-level
|
|
111
|
-
nonoka
|
|
111
|
+
nonoka init
|
|
112
112
|
|
|
113
113
|
# User-level
|
|
114
|
-
nonoka
|
|
114
|
+
nonoka init --global
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
3. Make sure your model API key is exported, then run:
|
|
@@ -132,7 +132,7 @@ Example output:
|
|
|
132
132
|
|
|
133
133
|
```
|
|
134
134
|
nonoka-cli doctor
|
|
135
|
-
✓ nonoka-cli 0.2.
|
|
135
|
+
✓ nonoka-cli 0.2.12
|
|
136
136
|
✓ Python 3.11
|
|
137
137
|
✓ opencode 1.18.2
|
|
138
138
|
✓ provider nonoka-opencode-provider@0.2.17
|
|
@@ -403,7 +403,7 @@ similarity score without storing raw cache queries. `max_total_tokens` and
|
|
|
403
403
|
is unavailable, `fail_on_unknown_cost: true` terminates the task rather than
|
|
404
404
|
silently exceeding the cost budget.
|
|
405
405
|
|
|
406
|
-
### `nonoka
|
|
406
|
+
### `nonoka init`
|
|
407
407
|
|
|
408
408
|
Generate or merge an `opencode.json` in the current directory and create
|
|
409
409
|
`.opencode/agents/build.md` from your nonoka `system_prompt`. The generated
|
|
@@ -412,7 +412,8 @@ nonoka config path to the backend.
|
|
|
412
412
|
|
|
413
413
|
## OpenCode configuration
|
|
414
414
|
|
|
415
|
-
`nonoka
|
|
415
|
+
`nonoka init` generates two things. `nonoka opencode init` remains as a
|
|
416
|
+
backward-compatible, explicit spelling:
|
|
416
417
|
|
|
417
418
|
1. `opencode.json` in the current directory, which wires OpenCode to the
|
|
418
419
|
`nonoka-opencode-provider` package and sets HITL permissions.
|
|
@@ -429,7 +430,7 @@ A typical generated `opencode.json` looks like:
|
|
|
429
430
|
"npm": "nonoka-opencode-provider",
|
|
430
431
|
"name": "Nonoka",
|
|
431
432
|
"options": {
|
|
432
|
-
"serverCommand": ["
|
|
433
|
+
"serverCommand": ["/path/to/python", "-m", "nonoka_cli", "--server"],
|
|
433
434
|
"cwd": ".",
|
|
434
435
|
"configPath": "~/.config/nonoka/config.yaml"
|
|
435
436
|
},
|
|
@@ -463,12 +464,12 @@ A typical generated `opencode.json` looks like:
|
|
|
463
464
|
|
|
464
465
|
The `"tools": {"skill": false}` line disables OpenCode's native `skill:<name>`
|
|
465
466
|
tool so it does not collide with nonoka's `load_skill` / `skill__<name>__<tool>`
|
|
466
|
-
workflow. `nonoka
|
|
467
|
+
workflow. `nonoka init` writes this automatically.
|
|
467
468
|
|
|
468
469
|
## Prompt ownership
|
|
469
470
|
|
|
470
471
|
Nonoka owns the canonical system prompt via `system_prompt` in
|
|
471
|
-
`~/.config/nonoka/config.yaml`. When you run `nonoka
|
|
472
|
+
`~/.config/nonoka/config.yaml`. When you run `nonoka init`, the
|
|
472
473
|
command adapts that prompt and writes it to `.opencode/agents/build.md` so
|
|
473
474
|
OpenCode uses it for its primary agent. OpenCode-specific guidelines (tool
|
|
474
475
|
names, approval behavior, path conventions) are appended automatically; they are
|
|
@@ -482,10 +483,9 @@ When running inside OpenCode, HITL is handled by OpenCode itself. The generated
|
|
|
482
483
|
Nonoka forwards OpenCode's native tool definitions to the model, approval
|
|
483
484
|
dialogs render natively for `bash`, `read`, `write`, and `edit` operations.
|
|
484
485
|
|
|
485
|
-
`nonoka
|
|
486
|
-
|
|
487
|
-
`
|
|
488
|
-
source of truth, add a `permissions` block and re-run `nonoka-cli opencode init`:
|
|
486
|
+
In OpenCode mode, `nonoka init` derives both generated permission blocks from
|
|
487
|
+
`cli.auto_approve` and optional `permissions` overrides in `nonoka.yaml`. Add a
|
|
488
|
+
`permissions` block and re-run `nonoka init` to keep YAML as the source of truth:
|
|
489
489
|
|
|
490
490
|
```yaml
|
|
491
491
|
permissions:
|
|
@@ -495,8 +495,9 @@ permissions:
|
|
|
495
495
|
edit: ask
|
|
496
496
|
```
|
|
497
497
|
|
|
498
|
-
`cli.auto_approve: true`
|
|
499
|
-
`
|
|
498
|
+
`cli.auto_approve: true` auto-allows the core coding tools, including read-only
|
|
499
|
+
`glob` and `grep`, before explicit overrides are applied. For standalone mode,
|
|
500
|
+
`hitl.policy` still controls Nonoka-owned tool approval.
|
|
500
501
|
|
|
501
502
|
## External-tools mode
|
|
502
503
|
|
|
@@ -511,7 +512,8 @@ default. OpenCode sends its native tool list (e.g. `bash`, `read`, `write`,
|
|
|
511
512
|
`Runner.resume_external_tools()`.
|
|
512
513
|
|
|
513
514
|
To start external-tools mode, run OpenCode with the generated `opencode.json`;
|
|
514
|
-
the provider spawns `
|
|
515
|
+
the provider automatically spawns `python -m nonoka_cli --server` with the
|
|
516
|
+
interpreter that generated the project config.
|
|
515
517
|
|
|
516
518
|
## MCP and Skill support
|
|
517
519
|
|
|
@@ -710,7 +712,8 @@ here because they affect the TUI/HITL experience but cannot be fixed inside
|
|
|
710
712
|
|
|
711
713
|
## Server logs and request traces
|
|
712
714
|
|
|
713
|
-
When running inside OpenCode, the provider spawns
|
|
715
|
+
When running inside OpenCode, the provider spawns the interpreter-pinned
|
|
716
|
+
`python -m nonoka_cli --server` command as a
|
|
714
717
|
long-lived NDJSON bridge. Server stderr is redirected by the provider to a
|
|
715
718
|
per-working-directory log file so it does not pollute OpenCode's TUI:
|
|
716
719
|
|
|
@@ -4,7 +4,7 @@ English | [简体中文](README.zh-CN.md)
|
|
|
4
4
|
|
|
5
5
|
OpenCode backend for the [Nonoka](https://pypi.org/project/nonoka/) Agent framework.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Nonoka runs as a stdio NDJSON bridge server (`python -m nonoka_cli --server`) that
|
|
8
8
|
talks to the `nonoka-opencode-provider` TypeScript package. When used inside
|
|
9
9
|
OpenCode, Nonoka acts as the conversation/decision backend while OpenCode owns
|
|
10
10
|
tool execution and human-in-the-loop (HITL) approval using its native tools.
|
|
@@ -28,18 +28,18 @@ After installing, configure your API key and run `nonoka`:
|
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
# Interactive: it will ask for your key and save it to ~/.config/nonoka/.env
|
|
31
|
-
nonoka
|
|
31
|
+
nonoka config init
|
|
32
32
|
|
|
33
33
|
# Or set it manually
|
|
34
34
|
export DEEPSEEK_API_KEY=<your-key>
|
|
35
35
|
|
|
36
|
-
nonoka
|
|
36
|
+
nonoka doctor
|
|
37
37
|
nonoka
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
`nonoka` is
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
`nonoka` is the primary command. It starts the OpenCode TUI when invoked without
|
|
41
|
+
a subcommand, while `nonoka run --message "<task>"` provides one-shot CLI usage.
|
|
42
|
+
The legacy `nonoka-cli` executable remains available for compatibility.
|
|
43
43
|
|
|
44
44
|
`nonoka-cli` automatically loads `~/.config/nonoka/.env` and `./.env` on startup,
|
|
45
45
|
so you don't need to `export` every time if you save the key in `.env`.
|
|
@@ -65,24 +65,24 @@ npm install -g nonoka-opencode-provider
|
|
|
65
65
|
`~/.config/nonoka/.env`):
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
nonoka
|
|
68
|
+
nonoka config init
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
For scripted setups, use the non-interactive mode (you'll still need to set the
|
|
72
72
|
API key via `.env` or `export`):
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
|
-
nonoka
|
|
75
|
+
nonoka config init --yes --model deepseek/deepseek-v4-pro
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
2. Generate an OpenCode config in the current project or globally:
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
81
|
# Project-level
|
|
82
|
-
nonoka
|
|
82
|
+
nonoka init
|
|
83
83
|
|
|
84
84
|
# User-level
|
|
85
|
-
nonoka
|
|
85
|
+
nonoka init --global
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
3. Make sure your model API key is exported, then run:
|
|
@@ -103,7 +103,7 @@ Example output:
|
|
|
103
103
|
|
|
104
104
|
```
|
|
105
105
|
nonoka-cli doctor
|
|
106
|
-
✓ nonoka-cli 0.2.
|
|
106
|
+
✓ nonoka-cli 0.2.12
|
|
107
107
|
✓ Python 3.11
|
|
108
108
|
✓ opencode 1.18.2
|
|
109
109
|
✓ provider nonoka-opencode-provider@0.2.17
|
|
@@ -374,7 +374,7 @@ similarity score without storing raw cache queries. `max_total_tokens` and
|
|
|
374
374
|
is unavailable, `fail_on_unknown_cost: true` terminates the task rather than
|
|
375
375
|
silently exceeding the cost budget.
|
|
376
376
|
|
|
377
|
-
### `nonoka
|
|
377
|
+
### `nonoka init`
|
|
378
378
|
|
|
379
379
|
Generate or merge an `opencode.json` in the current directory and create
|
|
380
380
|
`.opencode/agents/build.md` from your nonoka `system_prompt`. The generated
|
|
@@ -383,7 +383,8 @@ nonoka config path to the backend.
|
|
|
383
383
|
|
|
384
384
|
## OpenCode configuration
|
|
385
385
|
|
|
386
|
-
`nonoka
|
|
386
|
+
`nonoka init` generates two things. `nonoka opencode init` remains as a
|
|
387
|
+
backward-compatible, explicit spelling:
|
|
387
388
|
|
|
388
389
|
1. `opencode.json` in the current directory, which wires OpenCode to the
|
|
389
390
|
`nonoka-opencode-provider` package and sets HITL permissions.
|
|
@@ -400,7 +401,7 @@ A typical generated `opencode.json` looks like:
|
|
|
400
401
|
"npm": "nonoka-opencode-provider",
|
|
401
402
|
"name": "Nonoka",
|
|
402
403
|
"options": {
|
|
403
|
-
"serverCommand": ["
|
|
404
|
+
"serverCommand": ["/path/to/python", "-m", "nonoka_cli", "--server"],
|
|
404
405
|
"cwd": ".",
|
|
405
406
|
"configPath": "~/.config/nonoka/config.yaml"
|
|
406
407
|
},
|
|
@@ -434,12 +435,12 @@ A typical generated `opencode.json` looks like:
|
|
|
434
435
|
|
|
435
436
|
The `"tools": {"skill": false}` line disables OpenCode's native `skill:<name>`
|
|
436
437
|
tool so it does not collide with nonoka's `load_skill` / `skill__<name>__<tool>`
|
|
437
|
-
workflow. `nonoka
|
|
438
|
+
workflow. `nonoka init` writes this automatically.
|
|
438
439
|
|
|
439
440
|
## Prompt ownership
|
|
440
441
|
|
|
441
442
|
Nonoka owns the canonical system prompt via `system_prompt` in
|
|
442
|
-
`~/.config/nonoka/config.yaml`. When you run `nonoka
|
|
443
|
+
`~/.config/nonoka/config.yaml`. When you run `nonoka init`, the
|
|
443
444
|
command adapts that prompt and writes it to `.opencode/agents/build.md` so
|
|
444
445
|
OpenCode uses it for its primary agent. OpenCode-specific guidelines (tool
|
|
445
446
|
names, approval behavior, path conventions) are appended automatically; they are
|
|
@@ -453,10 +454,9 @@ When running inside OpenCode, HITL is handled by OpenCode itself. The generated
|
|
|
453
454
|
Nonoka forwards OpenCode's native tool definitions to the model, approval
|
|
454
455
|
dialogs render natively for `bash`, `read`, `write`, and `edit` operations.
|
|
455
456
|
|
|
456
|
-
`nonoka
|
|
457
|
-
|
|
458
|
-
`
|
|
459
|
-
source of truth, add a `permissions` block and re-run `nonoka-cli opencode init`:
|
|
457
|
+
In OpenCode mode, `nonoka init` derives both generated permission blocks from
|
|
458
|
+
`cli.auto_approve` and optional `permissions` overrides in `nonoka.yaml`. Add a
|
|
459
|
+
`permissions` block and re-run `nonoka init` to keep YAML as the source of truth:
|
|
460
460
|
|
|
461
461
|
```yaml
|
|
462
462
|
permissions:
|
|
@@ -466,8 +466,9 @@ permissions:
|
|
|
466
466
|
edit: ask
|
|
467
467
|
```
|
|
468
468
|
|
|
469
|
-
`cli.auto_approve: true`
|
|
470
|
-
`
|
|
469
|
+
`cli.auto_approve: true` auto-allows the core coding tools, including read-only
|
|
470
|
+
`glob` and `grep`, before explicit overrides are applied. For standalone mode,
|
|
471
|
+
`hitl.policy` still controls Nonoka-owned tool approval.
|
|
471
472
|
|
|
472
473
|
## External-tools mode
|
|
473
474
|
|
|
@@ -482,7 +483,8 @@ default. OpenCode sends its native tool list (e.g. `bash`, `read`, `write`,
|
|
|
482
483
|
`Runner.resume_external_tools()`.
|
|
483
484
|
|
|
484
485
|
To start external-tools mode, run OpenCode with the generated `opencode.json`;
|
|
485
|
-
the provider spawns `
|
|
486
|
+
the provider automatically spawns `python -m nonoka_cli --server` with the
|
|
487
|
+
interpreter that generated the project config.
|
|
486
488
|
|
|
487
489
|
## MCP and Skill support
|
|
488
490
|
|
|
@@ -681,7 +683,8 @@ here because they affect the TUI/HITL experience but cannot be fixed inside
|
|
|
681
683
|
|
|
682
684
|
## Server logs and request traces
|
|
683
685
|
|
|
684
|
-
When running inside OpenCode, the provider spawns
|
|
686
|
+
When running inside OpenCode, the provider spawns the interpreter-pinned
|
|
687
|
+
`python -m nonoka_cli --server` command as a
|
|
685
688
|
long-lived NDJSON bridge. Server stderr is redirected by the provider to a
|
|
686
689
|
per-working-directory log file so it does not pollute OpenCode's TUI:
|
|
687
690
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import importlib.metadata
|
|
6
7
|
import logging
|
|
7
8
|
import os
|
|
8
9
|
import sys
|
|
@@ -42,6 +43,14 @@ from nonoka_cli.utils.logging import setup_logging
|
|
|
42
43
|
logger = structlog.get_logger("nonoka_cli.cli")
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
def _version() -> str:
|
|
47
|
+
"""Return the installed CLI version, including editable installs."""
|
|
48
|
+
try:
|
|
49
|
+
return importlib.metadata.version("nonoka-cli")
|
|
50
|
+
except importlib.metadata.PackageNotFoundError:
|
|
51
|
+
return "unknown"
|
|
52
|
+
|
|
53
|
+
|
|
45
54
|
def _load_env_files() -> None:
|
|
46
55
|
"""Load .env files so config env-var substitution works transparently.
|
|
47
56
|
|
|
@@ -61,9 +70,14 @@ def _load_env_files() -> None:
|
|
|
61
70
|
def _build_parser() -> argparse.ArgumentParser:
|
|
62
71
|
"""Build the argument parser."""
|
|
63
72
|
parser = argparse.ArgumentParser(
|
|
64
|
-
prog="nonoka
|
|
73
|
+
prog="nonoka",
|
|
65
74
|
description="Terminal frontend for the Nonoka Agent framework",
|
|
66
75
|
)
|
|
76
|
+
parser.add_argument(
|
|
77
|
+
"--version",
|
|
78
|
+
action="version",
|
|
79
|
+
version=f"%(prog)s {_version()}",
|
|
80
|
+
)
|
|
67
81
|
parser.add_argument(
|
|
68
82
|
"--config",
|
|
69
83
|
type=Path,
|
|
@@ -96,6 +110,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
96
110
|
subparsers = parser.add_subparsers(dest="command")
|
|
97
111
|
config_cmd.add_subparser(subparsers)
|
|
98
112
|
doctor_cmd.add_subparser(subparsers)
|
|
113
|
+
opencode_cmd.add_init_subparser(subparsers)
|
|
99
114
|
opencode_cmd.add_subparser(subparsers)
|
|
100
115
|
plugin_cmd.add_subparser(subparsers)
|
|
101
116
|
run_cmd.add_subparser(subparsers)
|
|
@@ -116,28 +131,23 @@ def main() -> int:
|
|
|
116
131
|
parser = _build_parser()
|
|
117
132
|
args = parser.parse_args()
|
|
118
133
|
|
|
119
|
-
|
|
120
|
-
|
|
134
|
+
log_level = (
|
|
135
|
+
logging.DEBUG if args.debug
|
|
136
|
+
else logging.INFO if args.verbose
|
|
137
|
+
else logging.WARNING
|
|
138
|
+
)
|
|
139
|
+
setup_logging(level=log_level, console=args.verbose or args.debug)
|
|
121
140
|
|
|
141
|
+
# Load .env files after logging is configured so the debug-only path message
|
|
142
|
+
# does not leak into normal command output.
|
|
143
|
+
_load_env_files()
|
|
122
144
|
if args.server:
|
|
123
145
|
return server_main(config_path=args.config, model=args.model)
|
|
124
146
|
|
|
125
147
|
if args.command and getattr(args, "func", None):
|
|
126
|
-
log_level = (
|
|
127
|
-
logging.DEBUG if args.debug
|
|
128
|
-
else logging.INFO if args.verbose
|
|
129
|
-
else logging.WARNING
|
|
130
|
-
)
|
|
131
|
-
setup_logging(level=log_level, console=args.verbose or args.debug)
|
|
132
148
|
return args.func(args)
|
|
133
149
|
|
|
134
150
|
# No subcommand given: launch the OpenCode TUI by default.
|
|
135
|
-
log_level = (
|
|
136
|
-
logging.DEBUG if args.debug
|
|
137
|
-
else logging.INFO if args.verbose
|
|
138
|
-
else logging.WARNING
|
|
139
|
-
)
|
|
140
|
-
setup_logging(level=log_level, console=args.verbose or args.debug)
|
|
141
151
|
return run_cmd.launch_tui(args)
|
|
142
152
|
|
|
143
153
|
|
|
@@ -63,14 +63,24 @@ def _run(
|
|
|
63
63
|
timeout: float = 10.0,
|
|
64
64
|
check: bool = False,
|
|
65
65
|
) -> subprocess.CompletedProcess[str]:
|
|
66
|
-
"""Run a command
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
"""Run a diagnostic command without leaking OS/timeout exceptions."""
|
|
67
|
+
try:
|
|
68
|
+
return subprocess.run(
|
|
69
|
+
cmd,
|
|
70
|
+
capture_output=True,
|
|
71
|
+
text=True,
|
|
72
|
+
timeout=timeout,
|
|
73
|
+
check=check,
|
|
74
|
+
)
|
|
75
|
+
except subprocess.TimeoutExpired as exc:
|
|
76
|
+
return subprocess.CompletedProcess(
|
|
77
|
+
cmd,
|
|
78
|
+
124,
|
|
79
|
+
stdout=exc.stdout if isinstance(exc.stdout, str) else "",
|
|
80
|
+
stderr=f"timed out after {timeout:g}s",
|
|
81
|
+
)
|
|
82
|
+
except OSError as exc:
|
|
83
|
+
return subprocess.CompletedProcess(cmd, 127, stdout="", stderr=str(exc))
|
|
74
84
|
|
|
75
85
|
|
|
76
86
|
def check_nonoka_cli_version() -> CheckResult:
|
|
@@ -123,6 +133,12 @@ def check_opencode() -> CheckResult:
|
|
|
123
133
|
version = result.stdout.strip() if result.returncode == 0 else "unknown"
|
|
124
134
|
if result.returncode == 0:
|
|
125
135
|
return CheckResult("ok", f"opencode {version}")
|
|
136
|
+
if result.returncode == 124:
|
|
137
|
+
return CheckResult(
|
|
138
|
+
"error",
|
|
139
|
+
"opencode version check timed out after 5s",
|
|
140
|
+
"Run `opencode --version` directly; inspect proxy/startup configuration or reinstall OpenCode.",
|
|
141
|
+
)
|
|
126
142
|
return CheckResult(
|
|
127
143
|
"warn",
|
|
128
144
|
f"opencode found but returned an error (reported {version})",
|
|
@@ -7,6 +7,7 @@ import copy
|
|
|
7
7
|
import json
|
|
8
8
|
import shutil
|
|
9
9
|
import subprocess
|
|
10
|
+
import sys
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import Any
|
|
12
13
|
|
|
@@ -37,7 +38,15 @@ _PROVIDER_VERSION = _resolve_provider_version() or "0.2.17"
|
|
|
37
38
|
# Tool categories that nonoka-cli needs OpenCode to auto-approve when
|
|
38
39
|
# ``cli.auto_approve`` is enabled. These are OpenCode's native permission
|
|
39
40
|
# keys, not nonoka-cli's internal tool names.
|
|
40
|
-
_OPENCODE_AUTO_APPROVED_TOOLS = [
|
|
41
|
+
_OPENCODE_AUTO_APPROVED_TOOLS = [
|
|
42
|
+
"read",
|
|
43
|
+
"glob",
|
|
44
|
+
"grep",
|
|
45
|
+
"bash",
|
|
46
|
+
"edit",
|
|
47
|
+
"write",
|
|
48
|
+
"todowrite",
|
|
49
|
+
]
|
|
41
50
|
|
|
42
51
|
|
|
43
52
|
def _build_opencode_permission(config: CLIConfig) -> dict[str, str]:
|
|
@@ -57,9 +66,7 @@ def _build_opencode_permission(config: CLIConfig) -> dict[str, str]:
|
|
|
57
66
|
permission[tool] = action
|
|
58
67
|
|
|
59
68
|
# nonoka.yaml permissions take precedence over auto_approve defaults.
|
|
60
|
-
|
|
61
|
-
if user_permissions:
|
|
62
|
-
permission.update(user_permissions)
|
|
69
|
+
permission.update(config.permissions)
|
|
63
70
|
# OpenCode 1.18 migrates ``tools.skill=false`` to a top-level permission,
|
|
64
71
|
# but an agent-specific permission block overrides that migration. Deny it
|
|
65
72
|
# explicitly in both generated blocks so Build cannot prompt for the native
|
|
@@ -80,7 +87,7 @@ _DEFAULT_OPENCODE_CONFIG = {
|
|
|
80
87
|
"npm": "nonoka-opencode-provider",
|
|
81
88
|
"name": "Nonoka",
|
|
82
89
|
"options": {
|
|
83
|
-
"serverCommand": ["
|
|
90
|
+
"serverCommand": [sys.executable, "-m", "nonoka_cli", "--server"],
|
|
84
91
|
"cwd": ".",
|
|
85
92
|
"requireFocusedVerification": True,
|
|
86
93
|
"verificationEnforcement": "strict",
|
|
@@ -293,7 +300,12 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
293
300
|
if config.model:
|
|
294
301
|
provider_block["models"] = {"default": {"name": f"Nonoka {config.model}"}}
|
|
295
302
|
provider_block["options"]["configPath"] = str(resolved_config_path)
|
|
296
|
-
provider_block["options"]["serverCommand"] = [
|
|
303
|
+
provider_block["options"]["serverCommand"] = [
|
|
304
|
+
sys.executable,
|
|
305
|
+
"-m",
|
|
306
|
+
"nonoka_cli",
|
|
307
|
+
"--server",
|
|
308
|
+
]
|
|
297
309
|
|
|
298
310
|
if not isinstance(merged.get("provider"), dict):
|
|
299
311
|
merged["provider"] = {}
|
|
@@ -374,7 +386,7 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
374
386
|
)
|
|
375
387
|
|
|
376
388
|
print(f"Provider: {_PROVIDER_PACKAGE}@{_PROVIDER_VERSION} ready")
|
|
377
|
-
print("Ready. Then run: nonoka
|
|
389
|
+
print("Ready. Then run: nonoka run")
|
|
378
390
|
return 0
|
|
379
391
|
|
|
380
392
|
|
|
@@ -386,42 +398,56 @@ def _add_config_arg(parser: Any) -> None:
|
|
|
386
398
|
)
|
|
387
399
|
|
|
388
400
|
|
|
389
|
-
def
|
|
390
|
-
"""
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
401
|
+
def _configure_init_parser(parser: Any) -> None:
|
|
402
|
+
"""Add the shared options for ``nonoka init`` and ``opencode init``."""
|
|
403
|
+
_add_config_arg(parser)
|
|
404
|
+
parser.add_argument(
|
|
405
|
+
"--yes",
|
|
406
|
+
"-y",
|
|
407
|
+
action="store_true",
|
|
408
|
+
help="Non-interactive mode (default; kept for script compatibility)",
|
|
409
|
+
)
|
|
410
|
+
parser.add_argument(
|
|
394
411
|
"--cwd",
|
|
395
412
|
dest="cwd",
|
|
396
413
|
default=".",
|
|
397
414
|
help="Directory to write opencode.json into (default: current directory)",
|
|
398
415
|
)
|
|
399
|
-
|
|
416
|
+
parser.add_argument(
|
|
400
417
|
"--global",
|
|
401
418
|
dest="global_",
|
|
402
419
|
action="store_true",
|
|
403
420
|
help="Write to ~/.config/opencode/opencode.json instead of ./opencode.json",
|
|
404
421
|
)
|
|
422
|
+
parser.set_defaults(func=cmd_init)
|
|
405
423
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
init_parser.
|
|
410
|
-
"
|
|
411
|
-
"
|
|
412
|
-
action="store_true",
|
|
413
|
-
help="Non-interactive mode (default; kept for script compatibility)",
|
|
424
|
+
|
|
425
|
+
def add_init_subparser(subparsers: Any) -> None:
|
|
426
|
+
"""Register the concise top-level alias for OpenCode integration setup."""
|
|
427
|
+
init_parser = subparsers.add_parser(
|
|
428
|
+
"init",
|
|
429
|
+
help="Generate or merge OpenCode integration config",
|
|
414
430
|
)
|
|
415
|
-
init_parser
|
|
431
|
+
_configure_init_parser(init_parser)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def add_subparser(subparsers: Any) -> None:
|
|
435
|
+
"""Register the ``opencode`` subcommand and its children."""
|
|
436
|
+
opencode_parser = subparsers.add_parser("opencode", help="Manage OpenCode integration")
|
|
437
|
+
_add_config_arg(opencode_parser)
|
|
438
|
+
opencode_parser.add_argument(
|
|
416
439
|
"--cwd",
|
|
417
440
|
dest="cwd",
|
|
418
441
|
default=".",
|
|
419
442
|
help="Directory to write opencode.json into (default: current directory)",
|
|
420
443
|
)
|
|
421
|
-
|
|
444
|
+
opencode_parser.add_argument(
|
|
422
445
|
"--global",
|
|
423
446
|
dest="global_",
|
|
424
447
|
action="store_true",
|
|
425
448
|
help="Write to ~/.config/opencode/opencode.json instead of ./opencode.json",
|
|
426
449
|
)
|
|
427
|
-
|
|
450
|
+
|
|
451
|
+
opencode_subparsers = opencode_parser.add_subparsers(dest="opencode_command", required=True)
|
|
452
|
+
init_parser = opencode_subparsers.add_parser("init", help="Generate or merge opencode.json")
|
|
453
|
+
_configure_init_parser(init_parser)
|
|
@@ -18,7 +18,7 @@ from nonoka_cli.commands.doctor_cmd import (
|
|
|
18
18
|
check_provider,
|
|
19
19
|
)
|
|
20
20
|
from nonoka_cli.config.loader import ConfigLoader
|
|
21
|
-
from nonoka_cli.safety import SrtSandbox
|
|
21
|
+
from nonoka_cli.safety import PROCESS_SANDBOX_ENV, SrtSandbox
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def _has_opencode() -> bool:
|
|
@@ -40,7 +40,7 @@ def _ensure_opencode_config(args: argparse.Namespace, cwd: Path) -> int:
|
|
|
40
40
|
if _has_opencode_config(cwd):
|
|
41
41
|
return 0
|
|
42
42
|
|
|
43
|
-
print(f"No opencode.json found in {cwd}. Running 'nonoka
|
|
43
|
+
print(f"No opencode.json found in {cwd}. Running 'nonoka init'...", file=sys.stderr)
|
|
44
44
|
|
|
45
45
|
init_args = argparse.Namespace(
|
|
46
46
|
config=getattr(args, "config", None),
|
|
@@ -74,7 +74,7 @@ def _run_preflight(args: argparse.Namespace, cwd: Path) -> tuple[Path, Any] | No
|
|
|
74
74
|
if result.remedy:
|
|
75
75
|
print(f" Try: {result.remedy}", file=sys.stderr)
|
|
76
76
|
if failures:
|
|
77
|
-
print(f" Diagnose: nonoka
|
|
77
|
+
print(f" Diagnose: nonoka doctor --cwd {cwd}", file=sys.stderr)
|
|
78
78
|
return None
|
|
79
79
|
|
|
80
80
|
referenced = _referenced_config_path(cwd)
|
|
@@ -93,7 +93,7 @@ def _run_preflight(args: argparse.Namespace, cwd: Path) -> tuple[Path, Any] | No
|
|
|
93
93
|
print("Error: --config does not match the config recorded in opencode.json.", file=sys.stderr)
|
|
94
94
|
print(f" Requested: {requested}", file=sys.stderr)
|
|
95
95
|
print(f" OpenCode: {referenced}", file=sys.stderr)
|
|
96
|
-
print(f" Try: nonoka
|
|
96
|
+
print(f" Try: nonoka init --cwd {cwd} --config {requested}", file=sys.stderr)
|
|
97
97
|
return None
|
|
98
98
|
|
|
99
99
|
try:
|
|
@@ -189,7 +189,16 @@ def launch_tui(args: argparse.Namespace) -> int:
|
|
|
189
189
|
executable = srt.executable()
|
|
190
190
|
if not executable:
|
|
191
191
|
if config.safety.required:
|
|
192
|
-
print(
|
|
192
|
+
print(
|
|
193
|
+
"Error: the required OpenCode process-tree sandbox needs SRT, "
|
|
194
|
+
"but `srt` is unavailable.",
|
|
195
|
+
file=sys.stderr,
|
|
196
|
+
)
|
|
197
|
+
print(
|
|
198
|
+
"Install it with `npm install -g @anthropic-ai/sandbox-runtime`, "
|
|
199
|
+
"then run `nonoka doctor --check-sandbox`.",
|
|
200
|
+
file=sys.stderr,
|
|
201
|
+
)
|
|
193
202
|
return 1
|
|
194
203
|
else:
|
|
195
204
|
settings = srt.settings(cwd)
|
|
@@ -201,6 +210,10 @@ def launch_tui(args: argparse.Namespace) -> int:
|
|
|
201
210
|
# for the whole TUI process tree so a launch cannot race an upgrade,
|
|
202
211
|
# even if OpenCode reads update policy before the project config.
|
|
203
212
|
launch_env["OPENCODE_DISABLE_AUTOUPDATE"] = "1"
|
|
213
|
+
if settings is not None:
|
|
214
|
+
# The bridge and its custom tools inherit the outer SRT boundary.
|
|
215
|
+
# Mark ownership so they do not try to bootstrap a nested SRT.
|
|
216
|
+
launch_env[PROCESS_SANDBOX_ENV] = "srt"
|
|
204
217
|
result = subprocess.run(cmd, cwd=cwd, env=launch_env)
|
|
205
218
|
return result.returncode
|
|
206
219
|
except KeyboardInterrupt:
|