nonoka-cli 0.2.11__tar.gz → 0.2.13__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.13}/PKG-INFO +60 -26
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/README.md +59 -25
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/pyproject.toml +1 -1
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/__init__.py +1 -1
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/cli.py +42 -20
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/commands/config_cmd.py +24 -13
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/commands/doctor_cmd.py +24 -8
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/commands/opencode_cmd.py +52 -24
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/commands/run_cmd.py +18 -5
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/config/loader.py +6 -2
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/config/models.py +2 -1
- nonoka_cli-0.2.13/src/nonoka_cli/safety/__init__.py +20 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/safety/preflight.py +13 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/tools/builtins/file_tools.py +12 -2
- nonoka_cli-0.2.13/tests/unit/commands/test_cli_entrypoint.py +56 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/commands/test_config_cmd.py +7 -1
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/commands/test_doctor_cmd.py +16 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/commands/test_opencode_cmd.py +28 -2
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/commands/test_run_cmd.py +21 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/config/test_loader.py +4 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/config/test_models.py +11 -0
- nonoka_cli-0.2.13/tests/unit/safety/test_preflight.py +27 -0
- nonoka_cli-0.2.13/tests/unit/test_install_script.py +200 -0
- nonoka_cli-0.2.13/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.13}/.gitignore +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/LICENSE +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/NOTICE +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/__main__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/benchmark/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/benchmark/harbor.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/benchmark/swe_bench.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/benchmark/swe_environment.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/benchmark/swe_runner.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/benchmark/watchdog.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/bridge/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/bridge/events.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/bridge/handler.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/bridge/nonoka_tools.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/bridge/protocol.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/bridge/server.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/commands/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/commands/benchmark_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/commands/eval_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/commands/logs_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/commands/plugin_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/commands/sessions_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/config/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/config/manager.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/agent_factory.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/context.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/context_trimmer.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/git_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/mcp_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/namespaces.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/operational_signals.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/orchestrator.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/plugin_manifest.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/plugin_manifest_converter.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/project_agents.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/prompt_builder.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/repo_map_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/run_evidence.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/runner_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/scorecard.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/semantic_cache.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/session_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/task_state.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/tool_output_policy.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/core/tool_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/mcp/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/mcp/manager.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/mcp/models.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/safety/sandbox.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/sessions/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/sessions/manager.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/sessions/models.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/skills/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/tools/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/tools/builtins/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/tools/builtins/agent_tools.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/tools/loader.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/utils/__init__.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/utils/errors.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/utils/logging.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/src/nonoka_cli/utils/trace_logger.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/conftest.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/fixtures/bridge_external_capabilities.json +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/integration/test_server_external_capabilities.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/integration/test_watchdog_process_group.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/benchmark/test_harbor.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/benchmark/test_swe_environment.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/benchmark/test_watchdog.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/bridge/test_events.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/bridge/test_handler.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/bridge/test_nonoka_tools.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/bridge/test_package.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/bridge/test_protocol.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/bridge/test_server.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/commands/test_benchmark_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/commands/test_eval_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/commands/test_logs_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/commands/test_plugin_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/commands/test_swe_bench_cmd.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_agent_factory.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_context_trimmer.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_git_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_operational_signals.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_orchestrator.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_plugin_manifest.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_plugin_manifest_converter.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_project_agents.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_prompt_builder.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_repo_map_service.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_safety_policy.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_scorecard.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_semantic_cache.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_task_state.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/core/test_tool_output_policy.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/sessions/test_manager.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/tests/unit/utils/test_logging.py +0 -0
- {nonoka_cli-0.2.11 → nonoka_cli-0.2.13}/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.13
|
|
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.
|
|
@@ -51,24 +51,40 @@ The installer will:
|
|
|
51
51
|
1. Check Python 3.10+ and Node/npm.
|
|
52
52
|
2. Install or update OpenCode.
|
|
53
53
|
3. Install `nonoka-cli` and the OpenCode provider.
|
|
54
|
-
4.
|
|
54
|
+
4. Ask where to keep the installation, configuration, and npm packages.
|
|
55
|
+
5. Generate the Nonoka and project-level OpenCode configuration.
|
|
56
|
+
|
|
57
|
+
The prompts show what each directory contains and provide these defaults:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
Installation directory (Python environment, launchers, and npm tools; e.g. ~/nonoka)
|
|
61
|
+
[~/.local/share/nonoka]
|
|
62
|
+
Configuration directory (config.yaml and .env; e.g. ~/.config/nonoka)
|
|
63
|
+
[~/.config/nonoka]
|
|
64
|
+
npm prefix (OpenCode/provider global packages)
|
|
65
|
+
[~/.local/share/nonoka/npm]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Press Enter to accept a default, or type a path such as `~/tools/nonoka`.
|
|
69
|
+
The generated `INSTALL_DIR/bin/nonoka` launcher remembers all three paths, so
|
|
70
|
+
using its absolute path does not require any environment exports.
|
|
55
71
|
|
|
56
72
|
After installing, configure your API key and run `nonoka`:
|
|
57
73
|
|
|
58
74
|
```bash
|
|
59
75
|
# Interactive: it will ask for your key and save it to ~/.config/nonoka/.env
|
|
60
|
-
nonoka
|
|
76
|
+
nonoka config init
|
|
61
77
|
|
|
62
78
|
# Or set it manually
|
|
63
79
|
export DEEPSEEK_API_KEY=<your-key>
|
|
64
80
|
|
|
65
|
-
nonoka
|
|
81
|
+
nonoka doctor
|
|
66
82
|
nonoka
|
|
67
83
|
```
|
|
68
84
|
|
|
69
|
-
`nonoka` is
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
`nonoka` is the primary command. It starts the OpenCode TUI when invoked without
|
|
86
|
+
a subcommand, while `nonoka run --message "<task>"` provides one-shot CLI usage.
|
|
87
|
+
The legacy `nonoka-cli` executable remains available for compatibility.
|
|
72
88
|
|
|
73
89
|
`nonoka-cli` automatically loads `~/.config/nonoka/.env` and `./.env` on startup,
|
|
74
90
|
so you don't need to `export` every time if you save the key in `.env`.
|
|
@@ -76,6 +92,21 @@ so you don't need to `export` every time if you save the key in `.env`.
|
|
|
76
92
|
> To use `uv` instead of `pip`, or to run non-interactively, pass flags:
|
|
77
93
|
> `curl -fsSL https://nonoka.dev/install.sh | bash -s -- --uv --yes`.
|
|
78
94
|
|
|
95
|
+
For a non-interactive custom location, use flags (environment variables with
|
|
96
|
+
the same names are also supported):
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
bash install.sh --yes --uv --npm-opencode \
|
|
100
|
+
--install-dir ~/tools/nonoka \
|
|
101
|
+
--config-dir ~/.config/nonoka \
|
|
102
|
+
--npm-prefix ~/tools/nonoka/npm
|
|
103
|
+
|
|
104
|
+
~/tools/nonoka/bin/nonoka doctor
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The corresponding variables are `NONOKA_INSTALL_DIR`, `NONOKA_CONFIG_DIR`,
|
|
108
|
+
and `NONOKA_NPM_PREFIX`. CLI flags take precedence over environment variables.
|
|
109
|
+
|
|
79
110
|
## Manual installation
|
|
80
111
|
|
|
81
112
|
```bash
|
|
@@ -94,24 +125,24 @@ npm install -g nonoka-opencode-provider
|
|
|
94
125
|
`~/.config/nonoka/.env`):
|
|
95
126
|
|
|
96
127
|
```bash
|
|
97
|
-
nonoka
|
|
128
|
+
nonoka config init
|
|
98
129
|
```
|
|
99
130
|
|
|
100
131
|
For scripted setups, use the non-interactive mode (you'll still need to set the
|
|
101
132
|
API key via `.env` or `export`):
|
|
102
133
|
|
|
103
134
|
```bash
|
|
104
|
-
nonoka
|
|
135
|
+
nonoka config init --yes --model deepseek/deepseek-v4-pro
|
|
105
136
|
```
|
|
106
137
|
|
|
107
138
|
2. Generate an OpenCode config in the current project or globally:
|
|
108
139
|
|
|
109
140
|
```bash
|
|
110
141
|
# Project-level
|
|
111
|
-
nonoka
|
|
142
|
+
nonoka init
|
|
112
143
|
|
|
113
144
|
# User-level
|
|
114
|
-
nonoka
|
|
145
|
+
nonoka init --global
|
|
115
146
|
```
|
|
116
147
|
|
|
117
148
|
3. Make sure your model API key is exported, then run:
|
|
@@ -132,7 +163,7 @@ Example output:
|
|
|
132
163
|
|
|
133
164
|
```
|
|
134
165
|
nonoka-cli doctor
|
|
135
|
-
✓ nonoka-cli 0.2.
|
|
166
|
+
✓ nonoka-cli 0.2.13
|
|
136
167
|
✓ Python 3.11
|
|
137
168
|
✓ opencode 1.18.2
|
|
138
169
|
✓ provider nonoka-opencode-provider@0.2.17
|
|
@@ -403,7 +434,7 @@ similarity score without storing raw cache queries. `max_total_tokens` and
|
|
|
403
434
|
is unavailable, `fail_on_unknown_cost: true` terminates the task rather than
|
|
404
435
|
silently exceeding the cost budget.
|
|
405
436
|
|
|
406
|
-
### `nonoka
|
|
437
|
+
### `nonoka init`
|
|
407
438
|
|
|
408
439
|
Generate or merge an `opencode.json` in the current directory and create
|
|
409
440
|
`.opencode/agents/build.md` from your nonoka `system_prompt`. The generated
|
|
@@ -412,7 +443,8 @@ nonoka config path to the backend.
|
|
|
412
443
|
|
|
413
444
|
## OpenCode configuration
|
|
414
445
|
|
|
415
|
-
`nonoka
|
|
446
|
+
`nonoka init` generates two things. `nonoka opencode init` remains as a
|
|
447
|
+
backward-compatible, explicit spelling:
|
|
416
448
|
|
|
417
449
|
1. `opencode.json` in the current directory, which wires OpenCode to the
|
|
418
450
|
`nonoka-opencode-provider` package and sets HITL permissions.
|
|
@@ -429,7 +461,7 @@ A typical generated `opencode.json` looks like:
|
|
|
429
461
|
"npm": "nonoka-opencode-provider",
|
|
430
462
|
"name": "Nonoka",
|
|
431
463
|
"options": {
|
|
432
|
-
"serverCommand": ["
|
|
464
|
+
"serverCommand": ["/path/to/python", "-m", "nonoka_cli", "--server"],
|
|
433
465
|
"cwd": ".",
|
|
434
466
|
"configPath": "~/.config/nonoka/config.yaml"
|
|
435
467
|
},
|
|
@@ -463,12 +495,12 @@ A typical generated `opencode.json` looks like:
|
|
|
463
495
|
|
|
464
496
|
The `"tools": {"skill": false}` line disables OpenCode's native `skill:<name>`
|
|
465
497
|
tool so it does not collide with nonoka's `load_skill` / `skill__<name>__<tool>`
|
|
466
|
-
workflow. `nonoka
|
|
498
|
+
workflow. `nonoka init` writes this automatically.
|
|
467
499
|
|
|
468
500
|
## Prompt ownership
|
|
469
501
|
|
|
470
502
|
Nonoka owns the canonical system prompt via `system_prompt` in
|
|
471
|
-
`~/.config/nonoka/config.yaml`. When you run `nonoka
|
|
503
|
+
`~/.config/nonoka/config.yaml`. When you run `nonoka init`, the
|
|
472
504
|
command adapts that prompt and writes it to `.opencode/agents/build.md` so
|
|
473
505
|
OpenCode uses it for its primary agent. OpenCode-specific guidelines (tool
|
|
474
506
|
names, approval behavior, path conventions) are appended automatically; they are
|
|
@@ -482,10 +514,9 @@ When running inside OpenCode, HITL is handled by OpenCode itself. The generated
|
|
|
482
514
|
Nonoka forwards OpenCode's native tool definitions to the model, approval
|
|
483
515
|
dialogs render natively for `bash`, `read`, `write`, and `edit` operations.
|
|
484
516
|
|
|
485
|
-
`nonoka
|
|
486
|
-
|
|
487
|
-
`
|
|
488
|
-
source of truth, add a `permissions` block and re-run `nonoka-cli opencode init`:
|
|
517
|
+
In OpenCode mode, `nonoka init` derives both generated permission blocks from
|
|
518
|
+
`cli.auto_approve` and optional `permissions` overrides in `nonoka.yaml`. Add a
|
|
519
|
+
`permissions` block and re-run `nonoka init` to keep YAML as the source of truth:
|
|
489
520
|
|
|
490
521
|
```yaml
|
|
491
522
|
permissions:
|
|
@@ -495,8 +526,9 @@ permissions:
|
|
|
495
526
|
edit: ask
|
|
496
527
|
```
|
|
497
528
|
|
|
498
|
-
`cli.auto_approve: true`
|
|
499
|
-
`
|
|
529
|
+
`cli.auto_approve: true` auto-allows the core coding tools, including read-only
|
|
530
|
+
`glob` and `grep`, before explicit overrides are applied. For standalone mode,
|
|
531
|
+
`hitl.policy` still controls Nonoka-owned tool approval.
|
|
500
532
|
|
|
501
533
|
## External-tools mode
|
|
502
534
|
|
|
@@ -511,7 +543,8 @@ default. OpenCode sends its native tool list (e.g. `bash`, `read`, `write`,
|
|
|
511
543
|
`Runner.resume_external_tools()`.
|
|
512
544
|
|
|
513
545
|
To start external-tools mode, run OpenCode with the generated `opencode.json`;
|
|
514
|
-
the provider spawns `
|
|
546
|
+
the provider automatically spawns `python -m nonoka_cli --server` with the
|
|
547
|
+
interpreter that generated the project config.
|
|
515
548
|
|
|
516
549
|
## MCP and Skill support
|
|
517
550
|
|
|
@@ -710,7 +743,8 @@ here because they affect the TUI/HITL experience but cannot be fixed inside
|
|
|
710
743
|
|
|
711
744
|
## Server logs and request traces
|
|
712
745
|
|
|
713
|
-
When running inside OpenCode, the provider spawns
|
|
746
|
+
When running inside OpenCode, the provider spawns the interpreter-pinned
|
|
747
|
+
`python -m nonoka_cli --server` command as a
|
|
714
748
|
long-lived NDJSON bridge. Server stderr is redirected by the provider to a
|
|
715
749
|
per-working-directory log file so it does not pollute OpenCode's TUI:
|
|
716
750
|
|
|
@@ -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.
|
|
@@ -22,24 +22,40 @@ The installer will:
|
|
|
22
22
|
1. Check Python 3.10+ and Node/npm.
|
|
23
23
|
2. Install or update OpenCode.
|
|
24
24
|
3. Install `nonoka-cli` and the OpenCode provider.
|
|
25
|
-
4.
|
|
25
|
+
4. Ask where to keep the installation, configuration, and npm packages.
|
|
26
|
+
5. Generate the Nonoka and project-level OpenCode configuration.
|
|
27
|
+
|
|
28
|
+
The prompts show what each directory contains and provide these defaults:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
Installation directory (Python environment, launchers, and npm tools; e.g. ~/nonoka)
|
|
32
|
+
[~/.local/share/nonoka]
|
|
33
|
+
Configuration directory (config.yaml and .env; e.g. ~/.config/nonoka)
|
|
34
|
+
[~/.config/nonoka]
|
|
35
|
+
npm prefix (OpenCode/provider global packages)
|
|
36
|
+
[~/.local/share/nonoka/npm]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Press Enter to accept a default, or type a path such as `~/tools/nonoka`.
|
|
40
|
+
The generated `INSTALL_DIR/bin/nonoka` launcher remembers all three paths, so
|
|
41
|
+
using its absolute path does not require any environment exports.
|
|
26
42
|
|
|
27
43
|
After installing, configure your API key and run `nonoka`:
|
|
28
44
|
|
|
29
45
|
```bash
|
|
30
46
|
# Interactive: it will ask for your key and save it to ~/.config/nonoka/.env
|
|
31
|
-
nonoka
|
|
47
|
+
nonoka config init
|
|
32
48
|
|
|
33
49
|
# Or set it manually
|
|
34
50
|
export DEEPSEEK_API_KEY=<your-key>
|
|
35
51
|
|
|
36
|
-
nonoka
|
|
52
|
+
nonoka doctor
|
|
37
53
|
nonoka
|
|
38
54
|
```
|
|
39
55
|
|
|
40
|
-
`nonoka` is
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
`nonoka` is the primary command. It starts the OpenCode TUI when invoked without
|
|
57
|
+
a subcommand, while `nonoka run --message "<task>"` provides one-shot CLI usage.
|
|
58
|
+
The legacy `nonoka-cli` executable remains available for compatibility.
|
|
43
59
|
|
|
44
60
|
`nonoka-cli` automatically loads `~/.config/nonoka/.env` and `./.env` on startup,
|
|
45
61
|
so you don't need to `export` every time if you save the key in `.env`.
|
|
@@ -47,6 +63,21 @@ so you don't need to `export` every time if you save the key in `.env`.
|
|
|
47
63
|
> To use `uv` instead of `pip`, or to run non-interactively, pass flags:
|
|
48
64
|
> `curl -fsSL https://nonoka.dev/install.sh | bash -s -- --uv --yes`.
|
|
49
65
|
|
|
66
|
+
For a non-interactive custom location, use flags (environment variables with
|
|
67
|
+
the same names are also supported):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
bash install.sh --yes --uv --npm-opencode \
|
|
71
|
+
--install-dir ~/tools/nonoka \
|
|
72
|
+
--config-dir ~/.config/nonoka \
|
|
73
|
+
--npm-prefix ~/tools/nonoka/npm
|
|
74
|
+
|
|
75
|
+
~/tools/nonoka/bin/nonoka doctor
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The corresponding variables are `NONOKA_INSTALL_DIR`, `NONOKA_CONFIG_DIR`,
|
|
79
|
+
and `NONOKA_NPM_PREFIX`. CLI flags take precedence over environment variables.
|
|
80
|
+
|
|
50
81
|
## Manual installation
|
|
51
82
|
|
|
52
83
|
```bash
|
|
@@ -65,24 +96,24 @@ npm install -g nonoka-opencode-provider
|
|
|
65
96
|
`~/.config/nonoka/.env`):
|
|
66
97
|
|
|
67
98
|
```bash
|
|
68
|
-
nonoka
|
|
99
|
+
nonoka config init
|
|
69
100
|
```
|
|
70
101
|
|
|
71
102
|
For scripted setups, use the non-interactive mode (you'll still need to set the
|
|
72
103
|
API key via `.env` or `export`):
|
|
73
104
|
|
|
74
105
|
```bash
|
|
75
|
-
nonoka
|
|
106
|
+
nonoka config init --yes --model deepseek/deepseek-v4-pro
|
|
76
107
|
```
|
|
77
108
|
|
|
78
109
|
2. Generate an OpenCode config in the current project or globally:
|
|
79
110
|
|
|
80
111
|
```bash
|
|
81
112
|
# Project-level
|
|
82
|
-
nonoka
|
|
113
|
+
nonoka init
|
|
83
114
|
|
|
84
115
|
# User-level
|
|
85
|
-
nonoka
|
|
116
|
+
nonoka init --global
|
|
86
117
|
```
|
|
87
118
|
|
|
88
119
|
3. Make sure your model API key is exported, then run:
|
|
@@ -103,7 +134,7 @@ Example output:
|
|
|
103
134
|
|
|
104
135
|
```
|
|
105
136
|
nonoka-cli doctor
|
|
106
|
-
✓ nonoka-cli 0.2.
|
|
137
|
+
✓ nonoka-cli 0.2.13
|
|
107
138
|
✓ Python 3.11
|
|
108
139
|
✓ opencode 1.18.2
|
|
109
140
|
✓ provider nonoka-opencode-provider@0.2.17
|
|
@@ -374,7 +405,7 @@ similarity score without storing raw cache queries. `max_total_tokens` and
|
|
|
374
405
|
is unavailable, `fail_on_unknown_cost: true` terminates the task rather than
|
|
375
406
|
silently exceeding the cost budget.
|
|
376
407
|
|
|
377
|
-
### `nonoka
|
|
408
|
+
### `nonoka init`
|
|
378
409
|
|
|
379
410
|
Generate or merge an `opencode.json` in the current directory and create
|
|
380
411
|
`.opencode/agents/build.md` from your nonoka `system_prompt`. The generated
|
|
@@ -383,7 +414,8 @@ nonoka config path to the backend.
|
|
|
383
414
|
|
|
384
415
|
## OpenCode configuration
|
|
385
416
|
|
|
386
|
-
`nonoka
|
|
417
|
+
`nonoka init` generates two things. `nonoka opencode init` remains as a
|
|
418
|
+
backward-compatible, explicit spelling:
|
|
387
419
|
|
|
388
420
|
1. `opencode.json` in the current directory, which wires OpenCode to the
|
|
389
421
|
`nonoka-opencode-provider` package and sets HITL permissions.
|
|
@@ -400,7 +432,7 @@ A typical generated `opencode.json` looks like:
|
|
|
400
432
|
"npm": "nonoka-opencode-provider",
|
|
401
433
|
"name": "Nonoka",
|
|
402
434
|
"options": {
|
|
403
|
-
"serverCommand": ["
|
|
435
|
+
"serverCommand": ["/path/to/python", "-m", "nonoka_cli", "--server"],
|
|
404
436
|
"cwd": ".",
|
|
405
437
|
"configPath": "~/.config/nonoka/config.yaml"
|
|
406
438
|
},
|
|
@@ -434,12 +466,12 @@ A typical generated `opencode.json` looks like:
|
|
|
434
466
|
|
|
435
467
|
The `"tools": {"skill": false}` line disables OpenCode's native `skill:<name>`
|
|
436
468
|
tool so it does not collide with nonoka's `load_skill` / `skill__<name>__<tool>`
|
|
437
|
-
workflow. `nonoka
|
|
469
|
+
workflow. `nonoka init` writes this automatically.
|
|
438
470
|
|
|
439
471
|
## Prompt ownership
|
|
440
472
|
|
|
441
473
|
Nonoka owns the canonical system prompt via `system_prompt` in
|
|
442
|
-
`~/.config/nonoka/config.yaml`. When you run `nonoka
|
|
474
|
+
`~/.config/nonoka/config.yaml`. When you run `nonoka init`, the
|
|
443
475
|
command adapts that prompt and writes it to `.opencode/agents/build.md` so
|
|
444
476
|
OpenCode uses it for its primary agent. OpenCode-specific guidelines (tool
|
|
445
477
|
names, approval behavior, path conventions) are appended automatically; they are
|
|
@@ -453,10 +485,9 @@ When running inside OpenCode, HITL is handled by OpenCode itself. The generated
|
|
|
453
485
|
Nonoka forwards OpenCode's native tool definitions to the model, approval
|
|
454
486
|
dialogs render natively for `bash`, `read`, `write`, and `edit` operations.
|
|
455
487
|
|
|
456
|
-
`nonoka
|
|
457
|
-
|
|
458
|
-
`
|
|
459
|
-
source of truth, add a `permissions` block and re-run `nonoka-cli opencode init`:
|
|
488
|
+
In OpenCode mode, `nonoka init` derives both generated permission blocks from
|
|
489
|
+
`cli.auto_approve` and optional `permissions` overrides in `nonoka.yaml`. Add a
|
|
490
|
+
`permissions` block and re-run `nonoka init` to keep YAML as the source of truth:
|
|
460
491
|
|
|
461
492
|
```yaml
|
|
462
493
|
permissions:
|
|
@@ -466,8 +497,9 @@ permissions:
|
|
|
466
497
|
edit: ask
|
|
467
498
|
```
|
|
468
499
|
|
|
469
|
-
`cli.auto_approve: true`
|
|
470
|
-
`
|
|
500
|
+
`cli.auto_approve: true` auto-allows the core coding tools, including read-only
|
|
501
|
+
`glob` and `grep`, before explicit overrides are applied. For standalone mode,
|
|
502
|
+
`hitl.policy` still controls Nonoka-owned tool approval.
|
|
471
503
|
|
|
472
504
|
## External-tools mode
|
|
473
505
|
|
|
@@ -482,7 +514,8 @@ default. OpenCode sends its native tool list (e.g. `bash`, `read`, `write`,
|
|
|
482
514
|
`Runner.resume_external_tools()`.
|
|
483
515
|
|
|
484
516
|
To start external-tools mode, run OpenCode with the generated `opencode.json`;
|
|
485
|
-
the provider spawns `
|
|
517
|
+
the provider automatically spawns `python -m nonoka_cli --server` with the
|
|
518
|
+
interpreter that generated the project config.
|
|
486
519
|
|
|
487
520
|
## MCP and Skill support
|
|
488
521
|
|
|
@@ -681,7 +714,8 @@ here because they affect the TUI/HITL experience but cannot be fixed inside
|
|
|
681
714
|
|
|
682
715
|
## Server logs and request traces
|
|
683
716
|
|
|
684
|
-
When running inside OpenCode, the provider spawns
|
|
717
|
+
When running inside OpenCode, the provider spawns the interpreter-pinned
|
|
718
|
+
`python -m nonoka_cli --server` command as a
|
|
685
719
|
long-lived NDJSON bridge. Server stderr is redirected by the provider to a
|
|
686
720
|
per-working-directory log file so it does not pollute OpenCode's TUI:
|
|
687
721
|
|
|
@@ -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,17 +43,37 @@ from nonoka_cli.utils.logging import setup_logging
|
|
|
42
43
|
logger = structlog.get_logger("nonoka_cli.cli")
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
def
|
|
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
|
+
|
|
54
|
+
def _load_env_files(config_path: Path | str | None = None) -> None:
|
|
46
55
|
"""Load .env files so config env-var substitution works transparently.
|
|
47
56
|
|
|
48
57
|
Priority (lowest to highest):
|
|
49
|
-
1. ~/.config/nonoka/.env
|
|
50
|
-
2.
|
|
58
|
+
1. $NONOKA_CONFIG_DIR/.env or ~/.config/nonoka/.env
|
|
59
|
+
2. the explicit config file's sibling .env
|
|
60
|
+
3. ./.env
|
|
51
61
|
Existing environment variables always win.
|
|
52
62
|
"""
|
|
53
|
-
|
|
63
|
+
configured_dir = Path(
|
|
64
|
+
os.getenv("NONOKA_CONFIG_DIR", str(Path.home() / ".config" / "nonoka"))
|
|
65
|
+
).expanduser()
|
|
66
|
+
candidates = [configured_dir / ".env"]
|
|
67
|
+
if config_path is not None:
|
|
68
|
+
candidates.append(Path(config_path).expanduser().resolve().parent / ".env")
|
|
54
69
|
local_env = Path.cwd() / ".env"
|
|
55
|
-
|
|
70
|
+
candidates.append(local_env)
|
|
71
|
+
seen: set[Path] = set()
|
|
72
|
+
for path in candidates:
|
|
73
|
+
path = path.resolve()
|
|
74
|
+
if path in seen:
|
|
75
|
+
continue
|
|
76
|
+
seen.add(path)
|
|
56
77
|
if path.exists():
|
|
57
78
|
load_dotenv(dotenv_path=path, override=False)
|
|
58
79
|
logger.debug("loaded_env_file", path=str(path))
|
|
@@ -61,9 +82,14 @@ def _load_env_files() -> None:
|
|
|
61
82
|
def _build_parser() -> argparse.ArgumentParser:
|
|
62
83
|
"""Build the argument parser."""
|
|
63
84
|
parser = argparse.ArgumentParser(
|
|
64
|
-
prog="nonoka
|
|
85
|
+
prog="nonoka",
|
|
65
86
|
description="Terminal frontend for the Nonoka Agent framework",
|
|
66
87
|
)
|
|
88
|
+
parser.add_argument(
|
|
89
|
+
"--version",
|
|
90
|
+
action="version",
|
|
91
|
+
version=f"%(prog)s {_version()}",
|
|
92
|
+
)
|
|
67
93
|
parser.add_argument(
|
|
68
94
|
"--config",
|
|
69
95
|
type=Path,
|
|
@@ -96,6 +122,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
96
122
|
subparsers = parser.add_subparsers(dest="command")
|
|
97
123
|
config_cmd.add_subparser(subparsers)
|
|
98
124
|
doctor_cmd.add_subparser(subparsers)
|
|
125
|
+
opencode_cmd.add_init_subparser(subparsers)
|
|
99
126
|
opencode_cmd.add_subparser(subparsers)
|
|
100
127
|
plugin_cmd.add_subparser(subparsers)
|
|
101
128
|
run_cmd.add_subparser(subparsers)
|
|
@@ -116,28 +143,23 @@ def main() -> int:
|
|
|
116
143
|
parser = _build_parser()
|
|
117
144
|
args = parser.parse_args()
|
|
118
145
|
|
|
119
|
-
|
|
120
|
-
|
|
146
|
+
log_level = (
|
|
147
|
+
logging.DEBUG if args.debug
|
|
148
|
+
else logging.INFO if args.verbose
|
|
149
|
+
else logging.WARNING
|
|
150
|
+
)
|
|
151
|
+
setup_logging(level=log_level, console=args.verbose or args.debug)
|
|
121
152
|
|
|
153
|
+
# Load .env files after logging is configured so the debug-only path message
|
|
154
|
+
# does not leak into normal command output.
|
|
155
|
+
_load_env_files(args.config)
|
|
122
156
|
if args.server:
|
|
123
157
|
return server_main(config_path=args.config, model=args.model)
|
|
124
158
|
|
|
125
159
|
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
160
|
return args.func(args)
|
|
133
161
|
|
|
134
162
|
# 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
163
|
return run_cmd.launch_tui(args)
|
|
142
164
|
|
|
143
165
|
|
|
@@ -23,6 +23,16 @@ logger = structlog.get_logger("nonoka_cli.commands.config")
|
|
|
23
23
|
_GLOBAL_ENV_PATH = Path.home() / ".config" / "nonoka" / ".env"
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def _env_path_for_config(config_path: Path | None) -> Path:
|
|
27
|
+
"""Keep credentials beside an explicitly selected configuration file."""
|
|
28
|
+
if config_path is not None:
|
|
29
|
+
return config_path.expanduser().resolve().parent / ".env"
|
|
30
|
+
configured_dir = os.getenv("NONOKA_CONFIG_DIR")
|
|
31
|
+
if configured_dir:
|
|
32
|
+
return Path(configured_dir).expanduser().resolve() / ".env"
|
|
33
|
+
return _GLOBAL_ENV_PATH
|
|
34
|
+
|
|
35
|
+
|
|
26
36
|
def _load_manager(args: argparse.Namespace) -> ConfigManager:
|
|
27
37
|
"""Load or create a ConfigManager from the requested path."""
|
|
28
38
|
path = getattr(args, "config", None)
|
|
@@ -162,23 +172,23 @@ def _api_key_env_for_model(model: str) -> str:
|
|
|
162
172
|
return "OPENAI_API_KEY"
|
|
163
173
|
|
|
164
174
|
|
|
165
|
-
def _api_key_source_summary(env_var: str) -> str:
|
|
175
|
+
def _api_key_source_summary(env_var: str, env_path: Path = _GLOBAL_ENV_PATH) -> str:
|
|
166
176
|
"""Return a short description of where the API key is currently sourced."""
|
|
167
177
|
if os.getenv(env_var):
|
|
168
178
|
return f"from environment (${env_var})"
|
|
169
|
-
if _load_env_file(
|
|
170
|
-
return f"from {
|
|
179
|
+
if _load_env_file(env_path).get(env_var):
|
|
180
|
+
return f"from {env_path}"
|
|
171
181
|
return ""
|
|
172
182
|
|
|
173
183
|
|
|
174
|
-
def _collect_api_key(model: str) -> tuple[str, str, str]:
|
|
184
|
+
def _collect_api_key(model: str, env_path: Path = _GLOBAL_ENV_PATH) -> tuple[str, str, str]:
|
|
175
185
|
"""Collect API key info and optionally persist it.
|
|
176
186
|
|
|
177
187
|
Returns:
|
|
178
188
|
(env_var_name, api_key_for_config, summary_for_user)
|
|
179
189
|
"""
|
|
180
190
|
env_var = _api_key_env_for_model(model)
|
|
181
|
-
existing = os.getenv(env_var) or _load_env_file(
|
|
191
|
+
existing = os.getenv(env_var) or _load_env_file(env_path).get(env_var)
|
|
182
192
|
if existing:
|
|
183
193
|
print(f"Found {env_var} already set.")
|
|
184
194
|
return env_var, "", f"using existing ${env_var}"
|
|
@@ -203,16 +213,17 @@ def _collect_api_key(model: str) -> tuple[str, str, str]:
|
|
|
203
213
|
return env_var, "", "not saved"
|
|
204
214
|
|
|
205
215
|
# Default: save to .env
|
|
206
|
-
_write_env_file(
|
|
216
|
+
_write_env_file(env_path, env_var, key_input)
|
|
207
217
|
# Make it available for the rest of this process too.
|
|
208
218
|
os.environ[env_var] = key_input
|
|
209
|
-
print(f"Saved {env_var} to {
|
|
210
|
-
return env_var, "", f"saved to {
|
|
219
|
+
print(f"Saved {env_var} to {env_path}")
|
|
220
|
+
return env_var, "", f"saved to {env_path}"
|
|
211
221
|
|
|
212
222
|
|
|
213
223
|
def cmd_init(args: argparse.Namespace) -> int:
|
|
214
224
|
"""Create an initial nonoka config, interactively or with --yes defaults."""
|
|
215
|
-
path = Path(args.config) if args.config else ConfigLoader.DEFAULT_PATH
|
|
225
|
+
path = Path(args.config).expanduser() if args.config else ConfigLoader.DEFAULT_PATH
|
|
226
|
+
env_path = _env_path_for_config(path if args.config else None)
|
|
216
227
|
|
|
217
228
|
print(f"Creating nonoka configuration at: {path}")
|
|
218
229
|
|
|
@@ -220,7 +231,7 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
220
231
|
model = getattr(args, "model", None) or "deepseek/deepseek-v4-pro"
|
|
221
232
|
auto_approve = getattr(args, "auto_approve", False)
|
|
222
233
|
env_var = _api_key_env_for_model(model)
|
|
223
|
-
key_summary = _api_key_source_summary(env_var) or "not configured"
|
|
234
|
+
key_summary = _api_key_source_summary(env_var, env_path) or "not configured"
|
|
224
235
|
|
|
225
236
|
config = CLIConfig(
|
|
226
237
|
model=model,
|
|
@@ -240,9 +251,9 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
240
251
|
print(f"Configuration saved to {path}")
|
|
241
252
|
print(f"Model: {model}")
|
|
242
253
|
print(f"API key ({env_var}): {key_summary}")
|
|
243
|
-
if not os.getenv(env_var) and not _load_env_file(
|
|
254
|
+
if not os.getenv(env_var) and not _load_env_file(env_path).get(env_var):
|
|
244
255
|
print(
|
|
245
|
-
f"Set your API key with: nonoka
|
|
256
|
+
f"Set your API key with: nonoka config init (interactive) "
|
|
246
257
|
f"or export {env_var}=<your-key>"
|
|
247
258
|
)
|
|
248
259
|
return 0
|
|
@@ -254,7 +265,7 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
254
265
|
)
|
|
255
266
|
|
|
256
267
|
model = _read_input("Model identifier", "deepseek/deepseek-v4-pro")
|
|
257
|
-
env_var, api_key_value, key_summary = _collect_api_key(model)
|
|
268
|
+
env_var, api_key_value, key_summary = _collect_api_key(model, env_path)
|
|
258
269
|
|
|
259
270
|
system_prompt = _read_input(
|
|
260
271
|
"Optional system prompt",
|