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.
Files changed (124) hide show
  1. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/PKG-INFO +28 -25
  2. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/README.md +27 -24
  3. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/pyproject.toml +1 -1
  4. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/__init__.py +1 -1
  5. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/cli.py +25 -15
  6. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/doctor_cmd.py +24 -8
  7. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/opencode_cmd.py +50 -24
  8. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/run_cmd.py +18 -5
  9. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/config/models.py +2 -1
  10. nonoka_cli-0.2.12/src/nonoka_cli/safety/__init__.py +20 -0
  11. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/safety/preflight.py +13 -0
  12. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/tools/builtins/file_tools.py +12 -2
  13. nonoka_cli-0.2.12/tests/unit/commands/test_cli_entrypoint.py +42 -0
  14. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_doctor_cmd.py +16 -0
  15. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_opencode_cmd.py +28 -2
  16. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_run_cmd.py +21 -0
  17. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/config/test_models.py +11 -0
  18. nonoka_cli-0.2.12/tests/unit/safety/test_preflight.py +27 -0
  19. nonoka_cli-0.2.12/tests/unit/tools/test_file_tools.py +120 -0
  20. nonoka_cli-0.2.11/src/nonoka_cli/safety/__init__.py +0 -6
  21. nonoka_cli-0.2.11/tests/unit/safety/test_preflight.py +0 -14
  22. nonoka_cli-0.2.11/tests/unit/tools/test_file_tools.py +0 -60
  23. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/.gitignore +0 -0
  24. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/LICENSE +0 -0
  25. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/NOTICE +0 -0
  26. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/__main__.py +0 -0
  27. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/__init__.py +0 -0
  28. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/harbor.py +0 -0
  29. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/swe_bench.py +0 -0
  30. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/swe_environment.py +0 -0
  31. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/swe_runner.py +0 -0
  32. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/benchmark/watchdog.py +0 -0
  33. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/__init__.py +0 -0
  34. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/events.py +0 -0
  35. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/handler.py +0 -0
  36. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/nonoka_tools.py +0 -0
  37. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/protocol.py +0 -0
  38. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/bridge/server.py +0 -0
  39. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/__init__.py +0 -0
  40. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/benchmark_cmd.py +0 -0
  41. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/config_cmd.py +0 -0
  42. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/eval_cmd.py +0 -0
  43. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/logs_cmd.py +0 -0
  44. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/plugin_cmd.py +0 -0
  45. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/commands/sessions_cmd.py +0 -0
  46. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/config/__init__.py +0 -0
  47. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/config/loader.py +0 -0
  48. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/config/manager.py +0 -0
  49. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/__init__.py +0 -0
  50. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/agent_factory.py +0 -0
  51. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/context.py +0 -0
  52. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/context_trimmer.py +0 -0
  53. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/git_service.py +0 -0
  54. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/mcp_service.py +0 -0
  55. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/namespaces.py +0 -0
  56. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/operational_signals.py +0 -0
  57. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/orchestrator.py +0 -0
  58. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/plugin_manifest.py +0 -0
  59. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/plugin_manifest_converter.py +0 -0
  60. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/project_agents.py +0 -0
  61. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/prompt_builder.py +0 -0
  62. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/repo_map_service.py +0 -0
  63. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/run_evidence.py +0 -0
  64. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/runner_service.py +0 -0
  65. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/scorecard.py +0 -0
  66. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/semantic_cache.py +0 -0
  67. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/session_service.py +0 -0
  68. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/task_state.py +0 -0
  69. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/tool_output_policy.py +0 -0
  70. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/core/tool_service.py +0 -0
  71. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/mcp/__init__.py +0 -0
  72. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/mcp/manager.py +0 -0
  73. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/mcp/models.py +0 -0
  74. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/safety/sandbox.py +0 -0
  75. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/sessions/__init__.py +0 -0
  76. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/sessions/manager.py +0 -0
  77. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/sessions/models.py +0 -0
  78. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/skills/__init__.py +0 -0
  79. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/tools/__init__.py +0 -0
  80. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/tools/builtins/__init__.py +0 -0
  81. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/tools/builtins/agent_tools.py +0 -0
  82. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/tools/loader.py +0 -0
  83. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/utils/__init__.py +0 -0
  84. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/utils/errors.py +0 -0
  85. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/utils/logging.py +0 -0
  86. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/src/nonoka_cli/utils/trace_logger.py +0 -0
  87. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/conftest.py +0 -0
  88. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/fixtures/bridge_external_capabilities.json +0 -0
  89. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/integration/test_server_external_capabilities.py +0 -0
  90. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/integration/test_watchdog_process_group.py +0 -0
  91. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/benchmark/test_harbor.py +0 -0
  92. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/benchmark/test_swe_environment.py +0 -0
  93. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/benchmark/test_watchdog.py +0 -0
  94. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_events.py +0 -0
  95. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_handler.py +0 -0
  96. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_nonoka_tools.py +0 -0
  97. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_package.py +0 -0
  98. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_protocol.py +0 -0
  99. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/bridge/test_server.py +0 -0
  100. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_benchmark_cmd.py +0 -0
  101. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_config_cmd.py +0 -0
  102. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_eval_cmd.py +0 -0
  103. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_logs_cmd.py +0 -0
  104. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_plugin_cmd.py +0 -0
  105. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/commands/test_swe_bench_cmd.py +0 -0
  106. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/config/test_loader.py +0 -0
  107. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_agent_factory.py +0 -0
  108. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_context_trimmer.py +0 -0
  109. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_git_service.py +0 -0
  110. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_operational_signals.py +0 -0
  111. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_orchestrator.py +0 -0
  112. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_plugin_manifest.py +0 -0
  113. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_plugin_manifest_converter.py +0 -0
  114. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_project_agents.py +0 -0
  115. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_prompt_builder.py +0 -0
  116. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_repo_map_service.py +0 -0
  117. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_safety_policy.py +0 -0
  118. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_scorecard.py +0 -0
  119. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_semantic_cache.py +0 -0
  120. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_task_state.py +0 -0
  121. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/core/test_tool_output_policy.py +0 -0
  122. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/sessions/test_manager.py +0 -0
  123. {nonoka_cli-0.2.11 → nonoka_cli-0.2.12}/tests/unit/utils/test_logging.py +0 -0
  124. {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.11
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
- `nonoka-cli` runs as a stdio NDJSON bridge server (`nonoka-cli --server`) that
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-cli config init
60
+ nonoka config init
61
61
 
62
62
  # Or set it manually
63
63
  export DEEPSEEK_API_KEY=<your-key>
64
64
 
65
- nonoka-cli doctor
65
+ nonoka doctor
66
66
  nonoka
67
67
  ```
68
68
 
69
- `nonoka` is a shortcut for `nonoka-cli run` and starts the OpenCode TUI with the
70
- Nonoka backend. You can also use `nonoka run --message "<task>"` for one-shot
71
- CLI usage.
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-cli config init
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-cli config init --yes --model deepseek/deepseek-v4-pro
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-cli opencode init
111
+ nonoka init
112
112
 
113
113
  # User-level
114
- nonoka-cli opencode init --global
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.11
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-cli opencode init`
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-cli opencode init` generates two things:
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": ["bash", "-c", "nonoka-cli --server 2>/tmp/nonoka-server.log"],
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-cli opencode init` writes this automatically.
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-cli opencode init`, the
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-cli`'s own `cli.auto_approve` and `hitl.policy` settings only apply to
486
- standalone server / CLI mode. In OpenCode mode, permissions are governed by the
487
- `permission` block in `opencode.json`. To keep `nonoka.yaml` as the single
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` still auto-allows the core coding tools when no
499
- `permissions` block is present. For standalone mode, use `hitl.policy: auto`.
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 `nonoka-cli --server` automatically.
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 `nonoka-cli --server` as a
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
- `nonoka-cli` runs as a stdio NDJSON bridge server (`nonoka-cli --server`) that
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-cli config init
31
+ nonoka config init
32
32
 
33
33
  # Or set it manually
34
34
  export DEEPSEEK_API_KEY=<your-key>
35
35
 
36
- nonoka-cli doctor
36
+ nonoka doctor
37
37
  nonoka
38
38
  ```
39
39
 
40
- `nonoka` is a shortcut for `nonoka-cli run` and starts the OpenCode TUI with the
41
- Nonoka backend. You can also use `nonoka run --message "<task>"` for one-shot
42
- CLI usage.
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-cli config init
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-cli config init --yes --model deepseek/deepseek-v4-pro
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-cli opencode init
82
+ nonoka init
83
83
 
84
84
  # User-level
85
- nonoka-cli opencode init --global
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.11
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-cli opencode init`
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-cli opencode init` generates two things:
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": ["bash", "-c", "nonoka-cli --server 2>/tmp/nonoka-server.log"],
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-cli opencode init` writes this automatically.
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-cli opencode init`, the
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-cli`'s own `cli.auto_approve` and `hitl.policy` settings only apply to
457
- standalone server / CLI mode. In OpenCode mode, permissions are governed by the
458
- `permission` block in `opencode.json`. To keep `nonoka.yaml` as the single
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` still auto-allows the core coding tools when no
470
- `permissions` block is present. For standalone mode, use `hitl.policy: auto`.
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 `nonoka-cli --server` automatically.
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 `nonoka-cli --server` as a
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nonoka-cli"
7
- version = "0.2.11"
7
+ version = "0.2.12"
8
8
  description = "OpenCode backend for the Nonoka Agent framework"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -2,4 +2,4 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.2.11"
5
+ __version__ = "0.2.12"
@@ -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-cli",
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
- # Load .env files early so configuration and LLM providers see API keys.
120
- _load_env_files()
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 and return its result."""
67
- return subprocess.run(
68
- cmd,
69
- capture_output=True,
70
- text=True,
71
- timeout=timeout,
72
- check=check,
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 = ["read", "bash", "edit", "write", "todowrite"]
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
- user_permissions = getattr(config, "permissions", None)
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": ["nonoka-cli", "--server"],
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"] = ["nonoka-cli", "--server"]
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-cli run")
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 add_subparser(subparsers: Any) -> None:
390
- """Register the ``opencode`` subcommand and its children."""
391
- opencode_parser = subparsers.add_parser("opencode", help="Manage OpenCode integration")
392
- _add_config_arg(opencode_parser)
393
- opencode_parser.add_argument(
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
- opencode_parser.add_argument(
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
- opencode_subparsers = opencode_parser.add_subparsers(dest="opencode_command", required=True)
407
- init_parser = opencode_subparsers.add_parser("init", help="Generate or merge opencode.json")
408
- _add_config_arg(init_parser)
409
- init_parser.add_argument(
410
- "--yes",
411
- "-y",
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.add_argument(
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
- init_parser.add_argument(
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
- init_parser.set_defaults(func=cmd_init)
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-cli opencode init'...", file=sys.stderr)
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-cli doctor --cwd {cwd}", file=sys.stderr)
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-cli opencode init --cwd {cwd} --config {requested}", file=sys.stderr)
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("Error: required SRT sandbox is unavailable. Run `nonoka-cli doctor --check-sandbox`.", file=sys.stderr)
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: