nonoka-cli 0.2.4__tar.gz → 0.2.6__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 (120) hide show
  1. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/.gitignore +4 -1
  2. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/PKG-INFO +11 -7
  3. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/README.md +10 -6
  4. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/pyproject.toml +2 -1
  5. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/bridge/server.py +1 -3
  6. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/cli.py +4 -5
  7. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/commands/opencode_cmd.py +3 -2
  8. nonoka_cli-0.2.6/src/nonoka_cli/commands/run_cmd.py +110 -0
  9. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/config/models.py +3 -9
  10. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/agent_factory.py +6 -22
  11. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/orchestrator.py +1 -26
  12. nonoka_cli-0.2.6/src/nonoka_cli/core/planning_service.py +89 -0
  13. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/prompt_builder.py +4 -13
  14. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/tools/builtins/agent_tools.py +3 -3
  15. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/commands/test_opencode_cmd.py +0 -25
  16. nonoka_cli-0.2.6/tests/unit/commands/test_run_cmd.py +113 -0
  17. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/config/test_models.py +0 -27
  18. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/core/test_agent_factory.py +0 -46
  19. nonoka_cli-0.2.6/tests/unit/core/test_orchestrator.py +98 -0
  20. nonoka_cli-0.2.6/tests/unit/core/test_planning_service.py +40 -0
  21. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/uv.lock +214 -9
  22. nonoka_cli-0.2.4/.agents/skills/nonoka-tui-testing/SKILL.md +0 -413
  23. nonoka_cli-0.2.4/.agents/skills/nonoka-tui-testing/references/checklist.md +0 -48
  24. nonoka_cli-0.2.4/.agents/skills/nonoka-tui-testing/scripts/analyze-timeline.py +0 -100
  25. nonoka_cli-0.2.4/.agents/skills/nonoka-tui-testing/scripts/capture-screenshots.py +0 -191
  26. nonoka_cli-0.2.4/.agents/skills/nonoka-tui-testing/scripts/setup-sandbox.sh +0 -237
  27. nonoka_cli-0.2.4/.agents/skills/tui-testing/SKILL.md +0 -60
  28. nonoka_cli-0.2.4/.agents/skills/tui-testing/scripts/opencode_tui.py +0 -288
  29. nonoka_cli-0.2.4/.nonoka/repo_map.jsonl +0 -84
  30. nonoka_cli-0.2.4/docs/external-mcp-skill-design.md +0 -542
  31. nonoka_cli-0.2.4/docs/nonoka-cli-complex-task-evaluation.md +0 -254
  32. nonoka_cli-0.2.4/docs/nonoka-cli-refactor-design.md +0 -735
  33. nonoka_cli-0.2.4/docs/subagent-refactor-design.md +0 -162
  34. nonoka_cli-0.2.4/docs/subagent-refactor-plan.md +0 -134
  35. nonoka_cli-0.2.4/src/nonoka_cli/core/planning_service.py +0 -192
  36. nonoka_cli-0.2.4/src/nonoka_cli/core/review_service.py +0 -293
  37. nonoka_cli-0.2.4/src/nonoka_cli/core/subagent_service.py +0 -184
  38. nonoka_cli-0.2.4/tests/unit/core/test_orchestrator.py +0 -205
  39. nonoka_cli-0.2.4/tests/unit/core/test_planning_service.py +0 -174
  40. nonoka_cli-0.2.4/tests/unit/core/test_review_service.py +0 -251
  41. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/.nonoka/plugin.json.example +0 -0
  42. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/LICENSE +0 -0
  43. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/NOTICE +0 -0
  44. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/TODO.md +0 -0
  45. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/install.sh +0 -0
  46. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/nonoka.yaml +0 -0
  47. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/nonoka.yaml.example +0 -0
  48. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/.gitignore +0 -0
  49. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/LICENSE +0 -0
  50. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/README.md +0 -0
  51. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/bun.lock +0 -0
  52. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/package.json +0 -0
  53. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/src/index.ts +0 -0
  54. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/src/nonoka-language-model.ts +0 -0
  55. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/src/protocol.ts +0 -0
  56. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/src/stream.ts +0 -0
  57. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/tests/language-model.test.ts +0 -0
  58. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/tests/protocol.test.ts +0 -0
  59. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/tests/stream.test.ts +0 -0
  60. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/packages/nonoka-opencode-provider/tsconfig.json +0 -0
  61. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/scripts/quick-install.sh +0 -0
  62. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/__init__.py +0 -0
  63. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/__main__.py +0 -0
  64. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/bridge/__init__.py +0 -0
  65. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/bridge/events.py +0 -0
  66. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/bridge/handler.py +0 -0
  67. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/bridge/protocol.py +0 -0
  68. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/commands/__init__.py +0 -0
  69. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/commands/config_cmd.py +0 -0
  70. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/commands/doctor_cmd.py +0 -0
  71. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/commands/plugin_cmd.py +0 -0
  72. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/config/__init__.py +0 -0
  73. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/config/loader.py +0 -0
  74. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/config/manager.py +0 -0
  75. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/__init__.py +0 -0
  76. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/context.py +0 -0
  77. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/context_trimmer.py +0 -0
  78. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/git_service.py +0 -0
  79. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/mcp_service.py +0 -0
  80. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/namespaces.py +0 -0
  81. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/plugin_manifest.py +0 -0
  82. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/plugin_manifest_converter.py +0 -0
  83. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/repo_map_service.py +0 -0
  84. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/runner_service.py +0 -0
  85. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/session_service.py +0 -0
  86. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/task_state.py +0 -0
  87. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/tool_output_policy.py +0 -0
  88. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/core/tool_service.py +0 -0
  89. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/mcp/__init__.py +0 -0
  90. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/mcp/manager.py +0 -0
  91. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/mcp/models.py +0 -0
  92. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/sessions/__init__.py +0 -0
  93. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/sessions/manager.py +0 -0
  94. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/sessions/models.py +0 -0
  95. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/skills/__init__.py +0 -0
  96. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/tools/__init__.py +0 -0
  97. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/tools/builtins/__init__.py +0 -0
  98. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/tools/builtins/file_tools.py +0 -0
  99. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/tools/loader.py +0 -0
  100. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/utils/__init__.py +0 -0
  101. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/utils/errors.py +0 -0
  102. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/utils/logging.py +0 -0
  103. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/src/nonoka_cli/utils/trace_logger.py +0 -0
  104. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/conftest.py +0 -0
  105. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/integration/test_server_external_capabilities.py +0 -0
  106. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/bridge/test_events.py +0 -0
  107. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/bridge/test_handler.py +0 -0
  108. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/bridge/test_protocol.py +0 -0
  109. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/commands/test_config_cmd.py +0 -0
  110. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/commands/test_doctor_cmd.py +0 -0
  111. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/config/test_loader.py +0 -0
  112. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/core/test_context_trimmer.py +0 -0
  113. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/core/test_git_service.py +0 -0
  114. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/core/test_plugin_manifest.py +0 -0
  115. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/core/test_plugin_manifest_converter.py +0 -0
  116. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/core/test_repo_map_service.py +0 -0
  117. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/core/test_task_state.py +0 -0
  118. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/core/test_tool_output_policy.py +0 -0
  119. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/sessions/test_manager.py +0 -0
  120. {nonoka_cli-0.2.4 → nonoka_cli-0.2.6}/tests/unit/utils/test_trace_logger.py +0 -0
@@ -61,4 +61,7 @@ verdaccio-storage/
61
61
 
62
62
  # Private planning documents
63
63
  DESIGN.md
64
- REFACTOR.md
64
+ REFACTOR.md
65
+ # Runtime nonoka project artifacts
66
+ .nonoka/repo_map.jsonl
67
+ .nonoka/tasks/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonoka-cli
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: OpenCode backend for the Nonoka Agent framework
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -51,7 +51,7 @@ The installer will:
51
51
  3. Install `nonoka-cli` and the OpenCode provider.
52
52
  4. Generate `~/.config/nonoka/config.yaml` and `~/.config/opencode/opencode.json`.
53
53
 
54
- After installing, configure your API key and run `opencode`:
54
+ After installing, configure your API key and run `nonoka`:
55
55
 
56
56
  ```bash
57
57
  # Interactive: it will ask for your key and save it to ~/.config/nonoka/.env
@@ -61,9 +61,13 @@ nonoka-cli config init
61
61
  export DEEPSEEK_API_KEY=<your-key>
62
62
 
63
63
  nonoka-cli doctor
64
- opencode
64
+ nonoka
65
65
  ```
66
66
 
67
+ `nonoka` is a shortcut for `nonoka-cli run` and starts the OpenCode TUI with the
68
+ Nonoka backend. You can also use `nonoka run --message "<task>"` for one-shot
69
+ CLI usage.
70
+
67
71
  `nonoka-cli` automatically loads `~/.config/nonoka/.env` and `./.env` on startup,
68
72
  so you don't need to `export` every time if you save the key in `.env`.
69
73
 
@@ -111,7 +115,7 @@ nonoka-cli opencode init --global
111
115
  3. Make sure your model API key is exported, then run:
112
116
 
113
117
  ```bash
114
- opencode
118
+ nonoka
115
119
  ```
116
120
 
117
121
  ## `nonoka-cli doctor`
@@ -126,10 +130,10 @@ Example output:
126
130
 
127
131
  ```
128
132
  nonoka-cli doctor
129
- ✓ nonoka-cli 0.2.4
133
+ ✓ nonoka-cli 0.2.5
130
134
  ✓ Python 3.11
131
- ✓ opencode 1.17.13
132
- ✓ provider nonoka-opencode-provider@0.2.4
135
+ ✓ opencode 1.18.2
136
+ ✓ provider nonoka-opencode-provider@0.2.12
133
137
  ✓ config ~/.config/nonoka/config.yaml
134
138
  ✓ API key DEEPSEEK_API_KEY set
135
139
  ✓ OpenCode provider config in /home/user/.config/opencode/opencode.json
@@ -22,7 +22,7 @@ The installer will:
22
22
  3. Install `nonoka-cli` and the OpenCode provider.
23
23
  4. Generate `~/.config/nonoka/config.yaml` and `~/.config/opencode/opencode.json`.
24
24
 
25
- After installing, configure your API key and run `opencode`:
25
+ After installing, configure your API key and run `nonoka`:
26
26
 
27
27
  ```bash
28
28
  # Interactive: it will ask for your key and save it to ~/.config/nonoka/.env
@@ -32,9 +32,13 @@ nonoka-cli config init
32
32
  export DEEPSEEK_API_KEY=<your-key>
33
33
 
34
34
  nonoka-cli doctor
35
- opencode
35
+ nonoka
36
36
  ```
37
37
 
38
+ `nonoka` is a shortcut for `nonoka-cli run` and starts the OpenCode TUI with the
39
+ Nonoka backend. You can also use `nonoka run --message "<task>"` for one-shot
40
+ CLI usage.
41
+
38
42
  `nonoka-cli` automatically loads `~/.config/nonoka/.env` and `./.env` on startup,
39
43
  so you don't need to `export` every time if you save the key in `.env`.
40
44
 
@@ -82,7 +86,7 @@ nonoka-cli opencode init --global
82
86
  3. Make sure your model API key is exported, then run:
83
87
 
84
88
  ```bash
85
- opencode
89
+ nonoka
86
90
  ```
87
91
 
88
92
  ## `nonoka-cli doctor`
@@ -97,10 +101,10 @@ Example output:
97
101
 
98
102
  ```
99
103
  nonoka-cli doctor
100
- ✓ nonoka-cli 0.2.4
104
+ ✓ nonoka-cli 0.2.5
101
105
  ✓ Python 3.11
102
- ✓ opencode 1.17.13
103
- ✓ provider nonoka-opencode-provider@0.2.4
106
+ ✓ opencode 1.18.2
107
+ ✓ provider nonoka-opencode-provider@0.2.12
104
108
  ✓ config ~/.config/nonoka/config.yaml
105
109
  ✓ API key DEEPSEEK_API_KEY set
106
110
  ✓ OpenCode provider config in /home/user/.config/opencode/opencode.json
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nonoka-cli"
7
- version = "0.2.4"
7
+ version = "0.2.6"
8
8
  description = "OpenCode backend for the Nonoka Agent framework"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -29,6 +29,7 @@ dependencies = [
29
29
 
30
30
  [project.scripts]
31
31
  nonoka-cli = "nonoka_cli.cli:main"
32
+ nonoka = "nonoka_cli.cli:main"
32
33
 
33
34
  [project.optional-dependencies]
34
35
  dev = [
@@ -138,9 +138,7 @@ async def _async_main(
138
138
  ) -> int:
139
139
  """Set up async stdin/stdout and run the bridge server."""
140
140
  loop = asyncio.get_event_loop()
141
- # Large prompts (repo map, file contents) can produce single NDJSON lines
142
- # well above the default 64 KiB StreamReader limit. Bump it to 10 MiB.
143
- stdin = asyncio.StreamReader(limit=10 * 1024 * 1024)
141
+ stdin = asyncio.StreamReader()
144
142
  await loop.connect_read_pipe(lambda: asyncio.StreamReaderProtocol(stdin), sys.stdin)
145
143
 
146
144
  w_transport, w_protocol = await loop.connect_write_pipe(
@@ -26,7 +26,7 @@ import structlog
26
26
  from dotenv import load_dotenv
27
27
 
28
28
  from nonoka_cli.bridge.server import main as server_main
29
- from nonoka_cli.commands import config_cmd, doctor_cmd, opencode_cmd, plugin_cmd
29
+ from nonoka_cli.commands import config_cmd, doctor_cmd, opencode_cmd, plugin_cmd, run_cmd
30
30
  from nonoka_cli.utils.logging import setup_logging
31
31
 
32
32
  logger = structlog.get_logger("nonoka_cli.cli")
@@ -88,6 +88,7 @@ def _build_parser() -> argparse.ArgumentParser:
88
88
  doctor_cmd.add_subparser(subparsers)
89
89
  opencode_cmd.add_subparser(subparsers)
90
90
  plugin_cmd.add_subparser(subparsers)
91
+ run_cmd.add_subparser(subparsers)
91
92
 
92
93
  return parser
93
94
 
@@ -116,16 +117,14 @@ def main() -> int:
116
117
  setup_logging(level=log_level, console=args.verbose or args.debug)
117
118
  return args.func(args)
118
119
 
120
+ # No subcommand given: launch the OpenCode TUI by default.
119
121
  log_level = (
120
122
  logging.DEBUG if args.debug
121
123
  else logging.INFO if args.verbose
122
124
  else logging.WARNING
123
125
  )
124
126
  setup_logging(level=log_level, console=args.verbose or args.debug)
125
-
126
- print("nonoka-cli: use --server to start the OpenCode backend.", file=sys.stderr)
127
- parser.print_help(sys.stderr)
128
- return 0
127
+ return run_cmd.launch_tui(args)
129
128
 
130
129
 
131
130
  if __name__ == "__main__":
@@ -54,8 +54,9 @@ def _build_opencode_permission(config: CLIConfig) -> dict[str, str]:
54
54
  permission[tool] = action
55
55
 
56
56
  # nonoka.yaml permissions take precedence over auto_approve defaults.
57
- if config.permissions:
58
- permission.update(config.permissions)
57
+ user_permissions = getattr(config, "permissions", None)
58
+ if user_permissions:
59
+ permission.update(user_permissions)
59
60
  return permission
60
61
 
61
62
 
@@ -0,0 +1,110 @@
1
+ """Launch the OpenCode TUI from nonoka-cli."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from nonoka_cli.commands.opencode_cmd import cmd_init
13
+
14
+
15
+ def _has_opencode() -> bool:
16
+ """Return True if the opencode binary is available on PATH."""
17
+ return shutil.which("opencode") is not None
18
+
19
+
20
+ def _has_opencode_config(cwd: Path) -> bool:
21
+ """Return True if the directory already has an opencode.json."""
22
+ return (cwd / "opencode.json").exists()
23
+
24
+
25
+ def _ensure_opencode_config(args: argparse.Namespace, cwd: Path) -> int:
26
+ """Initialize opencode.json if it is missing.
27
+
28
+ Returns:
29
+ 0 if the config exists or was created successfully, otherwise an error code.
30
+ """
31
+ if _has_opencode_config(cwd):
32
+ return 0
33
+
34
+ print(f"No opencode.json found in {cwd}. Running 'nonoka-cli opencode init'...", file=sys.stderr)
35
+
36
+ init_args = argparse.Namespace(
37
+ config=getattr(args, "config", None),
38
+ cwd=str(cwd),
39
+ global_=False,
40
+ yes=True,
41
+ command="opencode",
42
+ opencode_command="init",
43
+ )
44
+ return cmd_init(init_args)
45
+
46
+
47
+ def launch_tui(args: argparse.Namespace) -> int:
48
+ """Launch the OpenCode TUI in the requested directory.
49
+
50
+ If ``args.message`` is provided, run OpenCode in one-shot mode with
51
+ ``opencode run --auto <message>``. Otherwise start the interactive TUI.
52
+ """
53
+ cwd = Path(getattr(args, "cwd", ".")).expanduser().resolve()
54
+
55
+ if not _has_opencode():
56
+ print("Error: opencode is not installed.", file=sys.stderr)
57
+ print("Install it with: npm install -g opencode", file=sys.stderr)
58
+ return 1
59
+
60
+ ret = _ensure_opencode_config(args, cwd)
61
+ if ret != 0:
62
+ return ret
63
+
64
+ message = getattr(args, "message", None)
65
+ if message:
66
+ cmd = ["opencode", "run", "--auto", message]
67
+ else:
68
+ cmd = ["opencode", str(cwd)]
69
+
70
+ try:
71
+ result = subprocess.run(cmd, cwd=cwd)
72
+ return result.returncode
73
+ except KeyboardInterrupt:
74
+ return 130
75
+ except FileNotFoundError:
76
+ print("Error: opencode disappeared during launch.", file=sys.stderr)
77
+ return 1
78
+
79
+
80
+ def cmd_run(args: argparse.Namespace) -> int:
81
+ """Entry point for the ``nonoka-cli run`` subcommand."""
82
+ return launch_tui(args)
83
+
84
+
85
+ def add_subparser(subparsers: Any) -> None:
86
+ """Register the ``run`` subcommand."""
87
+ run_parser = subparsers.add_parser(
88
+ "run",
89
+ help="Launch the OpenCode TUI (default when no subcommand is given)",
90
+ )
91
+ run_parser.add_argument(
92
+ "--config",
93
+ dest="config",
94
+ type=Path,
95
+ default=None,
96
+ help="Path to the nonoka config file (default: ~/.config/nonoka/config.yaml)",
97
+ )
98
+ run_parser.add_argument(
99
+ "--cwd",
100
+ dest="cwd",
101
+ default=".",
102
+ help="Directory to start OpenCode in (default: current directory)",
103
+ )
104
+ run_parser.add_argument(
105
+ "--message", "-m",
106
+ dest="message",
107
+ default=None,
108
+ help="Run a single message and exit instead of starting the interactive TUI",
109
+ )
110
+ run_parser.set_defaults(func=cmd_run)
@@ -111,20 +111,16 @@ class RepoMapConfig(BaseModel):
111
111
 
112
112
 
113
113
  class AgentRoleConfig(BaseModel):
114
- """Configuration for a single agent role (planner / reviewer)."""
114
+ """Configuration for a single agent role (planner / executor)."""
115
115
  model: str = ""
116
116
  max_turns: int = 5
117
117
  system_prompt: str = ""
118
118
 
119
119
 
120
120
  class AgentsConfig(BaseModel):
121
- """Multi-agent role configuration.
122
-
123
- The ``executor`` role has been removed: the main agent is the executor, and
124
- its ``max_turns`` is configured at the top level of ``CLIConfig``.
125
- """
121
+ """Multi-agent role configuration."""
126
122
  planner: AgentRoleConfig = Field(default_factory=AgentRoleConfig)
127
- reviewer: AgentRoleConfig = Field(default_factory=AgentRoleConfig)
123
+ executor: AgentRoleConfig = Field(default_factory=AgentRoleConfig)
128
124
 
129
125
 
130
126
  class PluginConfig(BaseModel):
@@ -146,11 +142,9 @@ class CLIConfig(BaseModel):
146
142
  Matches the expected structure of ~/.config/nonoka/config.yaml.
147
143
  """
148
144
  model: str = "gpt-4o"
149
- max_turns: int = 20
150
145
  system_prompt: str = ""
151
146
  api_key: str = ""
152
147
  base_url: str = ""
153
- permissions: dict[str, str] = Field(default_factory=dict)
154
148
  mcp_servers: dict[str, MCPServerConfigModel] = Field(default_factory=dict)
155
149
  tool_paths: list[Path] = Field(default_factory=list)
156
150
  skills: list[str] = Field(default_factory=list)
@@ -26,6 +26,7 @@ from nonoka_cli.config.models import CLIConfig
26
26
  from nonoka_cli.core.namespaces import (
27
27
  PrefixedCapability,
28
28
  mcp_tool_name,
29
+ sanitize_tool_name,
29
30
  skill_tool_name,
30
31
  )
31
32
  from nonoka_cli.core.prompt_builder import SystemPromptBuilder
@@ -143,8 +144,11 @@ class AgentFactory:
143
144
  return self._skill_registry
144
145
 
145
146
  def _executor_max_turns(self) -> int:
146
- """Return the configured main-agent max turns."""
147
- return self._config.max_turns
147
+ """Return the configured executor max turns, falling back to 20."""
148
+ configured = getattr(
149
+ self._config.agents.executor, "max_turns", None
150
+ )
151
+ return configured if configured else 20
148
152
 
149
153
  def build(
150
154
  self,
@@ -153,7 +157,6 @@ class AgentFactory:
153
157
  git_summary: str | None = None,
154
158
  plugin_summary: str | None = None,
155
159
  execution_plan: str | None = None,
156
- extra_tools: list[Capability] | None = None,
157
160
  ) -> Agent:
158
161
  """Build (or rebuild) an Agent from the current configuration.
159
162
 
@@ -164,9 +167,6 @@ class AgentFactory:
164
167
  repo_map: Optional repo-map block to inject into the system prompt.
165
168
  git_summary: Optional git-checkpoint status summary.
166
169
  plugin_summary: Optional loaded plugin manifest summary.
167
- execution_plan: Optional structured plan produced by the planner agent.
168
- extra_tools: Optional additional tools (e.g. sub-agent AgentTools) to
169
- register with the Agent.
170
170
 
171
171
  Returns:
172
172
  A nonoka Agent instance.
@@ -233,12 +233,6 @@ class AgentFactory:
233
233
  builder = builder.tool(capability)
234
234
  logger.debug("agent_factory_mcp_tools", count=len(mcp_tools))
235
235
 
236
- # Extra tools (e.g. planner/reviewer sub-agents) injected by Orchestrator.
237
- if extra_tools:
238
- for tool in extra_tools:
239
- builder = builder.tool(tool)
240
- logger.debug("agent_factory_extra_tools", count=len(extra_tools))
241
-
242
236
  self._agent = builder.build()
243
237
  return self._agent
244
238
 
@@ -274,7 +268,6 @@ class AgentFactory:
274
268
  git_summary: str | None = None,
275
269
  plugin_summary: str | None = None,
276
270
  execution_plan: str | None = None,
277
- extra_tools: list[Capability] | None = None,
278
271
  ) -> Agent:
279
272
  """Build an Agent for host-managed (e.g. OpenCode) mode.
280
273
 
@@ -296,9 +289,6 @@ class AgentFactory:
296
289
  in nonoka.yaml.
297
290
  external_mcp_servers: Optional host-managed MCP server definitions.
298
291
  external_skills: Optional host-managed skill definitions.
299
- execution_plan: Optional structured plan produced by the planner agent.
300
- extra_tools: Optional additional tools (e.g. sub-agent AgentTools) to
301
- register with the Agent.
302
292
  """
303
293
  if not self._config.model:
304
294
  raise AgentBuildError("No model configured. Set 'model' in config.yaml.")
@@ -454,12 +444,6 @@ class AgentFactory:
454
444
  for server, cap in mcp_tools:
455
445
  builder = builder.tool(PrefixedCapability(cap, f"mcp__{server}__"))
456
446
 
457
- # Extra tools (e.g. planner/reviewer sub-agents) injected by Orchestrator.
458
- if extra_tools:
459
- for tool in extra_tools:
460
- builder = builder.tool(tool)
461
- logger.debug("agent_factory_extra_tools", count=len(extra_tools))
462
-
463
447
  return builder.build()
464
448
 
465
449
  def _skill_registry_for_build(
@@ -26,7 +26,6 @@ from nonoka_cli.core.context_trimmer import TurnBasedContextTrimmer
26
26
  from nonoka_cli.core.git_service import GitService, build_git_service
27
27
  from nonoka_cli.core.mcp_service import MCPService
28
28
  from nonoka_cli.core.planning_service import PlanningService, build_planning_service
29
- from nonoka_cli.core.review_service import ReviewService, build_review_service
30
29
  from nonoka_cli.core.plugin_manifest import (
31
30
  PluginManifestLoader,
32
31
  format_manifest_summary,
@@ -99,7 +98,6 @@ class Orchestrator:
99
98
  self._git_service: GitService | None = None
100
99
  self._repo_map_service: RepoMapService | None = None
101
100
  self._planning_service: PlanningService | None = None
102
- self._review_service: ReviewService | None = None
103
101
  self._plugin_manifests: list[Any] = []
104
102
  self._initialized = False
105
103
 
@@ -162,10 +160,6 @@ class Orchestrator:
162
160
  working_dir=Path.cwd(),
163
161
  config=self._config,
164
162
  )
165
- self._review_service = build_review_service(
166
- working_dir=Path.cwd(),
167
- config=self._config,
168
- )
169
163
 
170
164
  # Load project plugin manifests before building the Agent so that
171
165
  # allowed_tools / plugin summary can be taken into account.
@@ -191,7 +185,6 @@ class Orchestrator:
191
185
  repo_map=repo_map_block,
192
186
  git_summary=git_summary,
193
187
  plugin_summary=plugin_summary,
194
- extra_tools=self._subagent_tools(),
195
188
  )
196
189
  self._tool_service = ToolService(self._agent_factory, self._tool_loader)
197
190
  self._mcp_service = MCPService(self._mcp_manager, self._agent_factory)
@@ -354,19 +347,6 @@ class Orchestrator:
354
347
  merged = merge_manifests(self._plugin_manifests)
355
348
  return merged.allowed_tools
356
349
 
357
- def _subagent_tools(self) -> list[Any]:
358
- """Return enabled sub-agent AgentTools for injection into the executor.
359
-
360
- Only planner/reviewer tools whose model is configured are returned, so the
361
- default executor tool list stays small.
362
- """
363
- tools: list[Any] = []
364
- if self._planning_service is not None and self._planning_service.enabled:
365
- tools.append(self._planning_service.agent_tool)
366
- if self._review_service is not None and self._review_service.enabled:
367
- tools.append(self._review_service.agent_tool)
368
- return tools
369
-
370
350
  def _on_config_changed(self, config: CLIConfig) -> None:
371
351
  """ConfigManager hot-reload listener: update local reference only."""
372
352
  self._config = config
@@ -450,10 +430,7 @@ class Orchestrator:
450
430
  logger.info("execute_plan_result", plan_preview=plan[:200] if plan else None)
451
431
  if plan and not plan.startswith(("Error", "Planning is disabled")):
452
432
  logger.info("execution_plan_generated", plan_length=len(plan))
453
- agent = self._agent_factory.build(
454
- execution_plan=plan,
455
- extra_tools=self._subagent_tools(),
456
- )
433
+ agent = self._agent_factory.build(execution_plan=plan)
457
434
  except Exception as exc:
458
435
  logger.warning("execution_plan_generation_failed", error=str(exc))
459
436
 
@@ -560,7 +537,6 @@ class Orchestrator:
560
537
  git_summary=git_summary,
561
538
  plugin_summary=plugin_summary,
562
539
  execution_plan=execution_plan,
563
- extra_tools=self._subagent_tools(),
564
540
  )
565
541
 
566
542
  logger.info(
@@ -618,7 +594,6 @@ class Orchestrator:
618
594
  repo_map=repo_map_block,
619
595
  git_summary=git_summary,
620
596
  plugin_summary=plugin_summary,
621
- extra_tools=self._subagent_tools(),
622
597
  )
623
598
 
624
599
  logger.info(
@@ -0,0 +1,89 @@
1
+ """Planning service for nonoka-cli.
2
+
3
+ Wires planner/executor configuration (``agents.planner``) to the orchestrator.
4
+ The actual planner implementation has been removed from nonoka-agent in 1.3.4;
5
+ nonoka-cli will provide its own LLM-based planner here in a future release.
6
+ For now the service reports itself as disabled when invoked so the orchestrator
7
+ falls back to single-agent execution.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+
14
+ from nonoka_cli.config.models import AgentsConfig, CLIConfig
15
+
16
+
17
+ class PlanningService:
18
+ """Adapts the nonoka-agent planner tool to CLI config."""
19
+
20
+ def __init__(
21
+ self,
22
+ working_dir: Path,
23
+ config: AgentsConfig | None = None,
24
+ default_model: str | None = None,
25
+ ):
26
+ self._working_dir = working_dir
27
+ self._config = config or AgentsConfig()
28
+ self._default_model = default_model
29
+
30
+ @property
31
+ def enabled(self) -> bool:
32
+ return bool(self.planner_model)
33
+
34
+ @property
35
+ def planner_model(self) -> str:
36
+ """Return the effective planner model identifier."""
37
+ configured = self._config.planner.model
38
+ if configured:
39
+ return configured
40
+ if self._default_model:
41
+ return self._default_model
42
+ return ""
43
+
44
+ @property
45
+ def executor_model(self) -> str:
46
+ """Return the effective executor model identifier."""
47
+ configured = self._config.executor.model
48
+ if configured:
49
+ return configured
50
+ if self._default_model:
51
+ return self._default_model
52
+ return ""
53
+
54
+ @property
55
+ def planner_max_turns(self) -> int:
56
+ return self._config.planner.max_turns
57
+
58
+ @property
59
+ def executor_max_turns(self) -> int:
60
+ return self._config.executor.max_turns
61
+
62
+ async def plan(self, task: str, max_steps: int = 10) -> str:
63
+ """Generate a structured plan for *task*.
64
+
65
+ Returns a human-readable plan or an error message. The planner tool has
66
+ been removed from nonoka-agent in 1.3.4; a CLI-native planner will replace
67
+ this stub in a future release.
68
+ """
69
+ if not self.enabled:
70
+ return "Planning is disabled: no planner model configured."
71
+ return (
72
+ "Planning is disabled: the built-in planner tool was removed from "
73
+ "nonoka-agent 1.3.4. Configure a planner model to opt into the future "
74
+ "CLI-native planner implementation."
75
+ )
76
+
77
+
78
+ def build_planning_service(
79
+ working_dir: Path,
80
+ config: CLIConfig | None = None,
81
+ ) -> PlanningService:
82
+ """Factory for creating a PlanningService from CLI configuration."""
83
+ agents_config = config.agents if config is not None else AgentsConfig()
84
+ default_model = config.model if config is not None else None
85
+ return PlanningService(
86
+ working_dir=working_dir,
87
+ config=agents_config,
88
+ default_model=default_model,
89
+ )
@@ -181,20 +181,11 @@ class SystemPromptBuilder:
181
181
  def _build_planner_block(self) -> str:
182
182
  """Return guidance for the planner/executor workflow.
183
183
 
184
- The ``plan_task`` sub-agent tool is injected only when ``agents.planner.model``
185
- is configured. The workflow reminder is included so the model knows to use
186
- planning for complex tasks and to synchronize progress with OpenCode's TODO
187
- UI via ``todowrite``.
184
+ The built-in ``plan_task`` tool was removed from nonoka-agent in 1.3.4; the
185
+ planner block is currently suppressed until nonoka-cli provides its own
186
+ LLM-based planner implementation.
188
187
  """
189
- if "plan_task" in self._base.lower():
190
- return ""
191
- return (
192
- "## Planner / Executor Workflow\n"
193
- "For complex multi-file or multi-step tasks, first call the ``plan_task`` "
194
- "tool to produce a structured plan. Then execute the steps using the "
195
- "file, search, and run-command tools. After each step, call ``todowrite`` "
196
- "to update the TODO list so the user can follow progress in the OpenCode TUI."
197
- )
188
+ return ""
198
189
 
199
190
  def _build_execution_plan_block(self) -> str:
200
191
  """Return the concrete execution plan produced by the planner agent."""
@@ -1,9 +1,9 @@
1
1
  """Agent-level tools re-exported from nonoka-agent.
2
2
 
3
3
  These tools are implemented in nonoka-agent (the core agent framework) and are
4
- made available to every nonoka-cli Agent. The ``plan_task`` tool has been moved
5
- to ``nonoka_cli.core.planning_service`` and is injected as a sub-agent AgentTool
6
- by the Orchestrator when ``agents.planner.model`` is configured.
4
+ made available to every nonoka-cli Agent. Keeping the implementation in
5
+ nonoka-agent lets other consumers reuse them, while this module declares the
6
+ subset the CLI exposes by default.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
@@ -151,28 +151,3 @@ def test_opencode_init_disables_native_skill_tool(tmp_path: Path):
151
151
 
152
152
  data = json.loads((tmp_path / "opencode.json").read_text())
153
153
  assert data.get("tools", {}).get("skill") is False
154
-
155
-
156
- def test_opencode_init_permissions_override(tmp_path: Path):
157
- config_path = tmp_path / "nonoka.yaml"
158
- ConfigLoader.save(
159
- CLIConfig(
160
- model="deepseek-chat",
161
- permissions={"read": "allow", "bash": "ask"},
162
- ),
163
- config_path,
164
- )
165
-
166
- args = argparse.Namespace(
167
- config=str(config_path),
168
- cwd=str(tmp_path),
169
- global_=False,
170
- )
171
- assert cmd_init(args) == 0
172
-
173
- data = json.loads((tmp_path / "opencode.json").read_text())
174
- assert data["permission"]["read"] == "allow"
175
- assert data["permission"]["bash"] == "ask"
176
- assert data["permission"]["write"] == "ask"
177
- assert data["agent"]["build"]["permission"]["read"] == "allow"
178
- assert data["agent"]["build"]["permission"]["bash"] == "ask"