@scemoon/cdh 1.0.0 → 1.0.3
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.
- package/cdh/__pycache__/__main__.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
- package/cdh/cli.py +473 -87
- package/cdh/scaffold.py +386 -121
- package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
- package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
- package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
- package/onecode/agent/agents/types.py +123 -79
- package/onecode/agent/cdh_loader.py +26 -13
- package/onecode/agent/engine.py +426 -235
- package/onecode/agent/onecode_agent_acp.py +200 -73
- package/onecode/agent/session.py +8 -13
- package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/agent_tools.py +4 -4
- package/onecode/agent/tools/todo_tools.py +188 -0
- package/onecode/cli.py +3 -3
- package/onecode/commands.py +439 -194
- package/onecode/config.py +3 -3
- package/onecode/config_screen.py +4 -2
- package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
- package/onecode/models/errors.py +11 -1
- package/onecode/models/messages.py +35 -16
- package/onecode/models/provider.py +243 -12
- package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/anthropic_provider.py +4 -2
- package/onecode/models/providers/deepseek_provider.py +2 -0
- package/onecode/models/providers/glm_provider.py +2 -1
- package/onecode/models/providers/minimax_provider.py +2 -1
- package/onecode/models/providers/minimaxi_provider.py +76 -25
- package/onecode/models/providers/ollama_provider.py +2 -1
- package/onecode/models/providers/openai_provider.py +4 -0
- package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/models.cpython-314.pyc +0 -0
- package/onecode/tasks/manager.py +1 -1
- package/package.json +1 -1
- package/pyproject.toml +10 -0
- package/run.js +24 -7
- package/tui/__pycache__/app.cpython-314.pyc +0 -0
- package/tui/__pycache__/cli.cpython-314.pyc +0 -0
- package/tui/__pycache__/db.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
- package/tui/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/__pycache__/paths.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
- package/tui/acp/agent.py +60 -11
- package/tui/acp/api.py +6 -0
- package/tui/acp/messages.py +11 -0
- package/tui/acp/prompt.py +1 -1
- package/tui/acp/protocol.py +31 -0
- package/tui/app.py +60 -8
- package/tui/db.py +23 -0
- package/tui/directory_watcher.py +35 -1
- package/tui/messages.py +4 -1
- package/tui/paths.py +3 -3
- package/tui/screens/__pycache__/agent_modal.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/component_picker.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_app.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/settings.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
- package/tui/screens/component_picker.py +148 -0
- package/tui/screens/main.py +47 -95
- package/tui/screens/projects.tcss +10 -0
- package/tui/screens/projects_app.py +33 -5
- package/tui/screens/projects_screen.py +171 -9
- package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent_screen.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
- package/tui/widgets/ask_user.py +1 -1
- package/tui/widgets/conversation.py +99 -42
- package/tui/widgets/modified_files.py +155 -23
- package/tui/widgets/plan.py +22 -0
- package/tui/widgets/project_directory_tree.py +22 -10
- package/tui/widgets/project_grid_select.py +23 -1
- package/tui/widgets/question.py +1 -1
- package/tui/widgets/subagent.py +202 -36
- package/tui/widgets/subagent_screen.py +186 -0
- package/tui/widgets/tool_call.py +5 -8
- package/onecode/agent/tools/task_tools.py +0 -249
package/cdh/cli.py
CHANGED
|
@@ -3,31 +3,38 @@ from typing import Optional
|
|
|
3
3
|
|
|
4
4
|
import click
|
|
5
5
|
|
|
6
|
-
from cdh.scaffold import
|
|
6
|
+
from cdh.scaffold import (
|
|
7
|
+
COMPONENTS,
|
|
8
|
+
COMPONENT_BY_ID,
|
|
9
|
+
CROSS_CUTTING,
|
|
10
|
+
CROSS_CUTTING_BY_ID,
|
|
11
|
+
add_component,
|
|
12
|
+
add_cross_cutting,
|
|
13
|
+
init_dlc_project,
|
|
14
|
+
scaffold_dlc_project,
|
|
15
|
+
)
|
|
7
16
|
from onecode.cli import cli as onecode_cli
|
|
8
17
|
from onecode.cli import setup_logging
|
|
9
18
|
from onecode.config import ensure_dirs, load_config, save_config
|
|
10
19
|
|
|
11
20
|
|
|
12
|
-
_CDH_DIR = Path.home() / ".
|
|
21
|
+
_CDH_DIR = Path.home() / ".cdh"
|
|
13
22
|
|
|
14
23
|
_COMMON_HELP = """
|
|
15
24
|
\b
|
|
16
25
|
Usage:
|
|
17
|
-
cdh
|
|
18
|
-
cdh tui
|
|
19
|
-
cdh
|
|
20
|
-
cdh
|
|
21
|
-
cdh
|
|
22
|
-
cdh
|
|
23
|
-
cdh project Project management
|
|
24
|
-
cdh version Show version information
|
|
26
|
+
cdh Launch TUI (agent store)
|
|
27
|
+
cdh tui Launch TUI (agent store)
|
|
28
|
+
cdh onecode <sub> onecode CLI surface (config / codebase / skill / mcp / help)
|
|
29
|
+
cdh project Project management
|
|
30
|
+
cdh session list|load Session management
|
|
31
|
+
cdh version Show version information
|
|
25
32
|
|
|
26
33
|
\b
|
|
27
34
|
Paths:
|
|
28
|
-
Config ~/.
|
|
29
|
-
Logs ~/.
|
|
30
|
-
Projects ~/.
|
|
35
|
+
Config ~/.cdh/onecode.config.yaml
|
|
36
|
+
Logs ~/.cdh/logs/
|
|
37
|
+
Projects ~/.cdh/projects/
|
|
31
38
|
"""
|
|
32
39
|
|
|
33
40
|
|
|
@@ -45,8 +52,11 @@ def cli(ctx):
|
|
|
45
52
|
CDH (Cloud Dev Harness) is an AI agent framework with LLM provider
|
|
46
53
|
integration, session management, and a Textual-based TUI.
|
|
47
54
|
|
|
48
|
-
Run without arguments to open the agent store
|
|
49
|
-
|
|
55
|
+
Run without arguments to open the agent store, where you can pick
|
|
56
|
+
a launcher agent.
|
|
57
|
+
|
|
58
|
+
All onecode-specific commands live under `cdh onecode <sub>` — e.g.
|
|
59
|
+
`cdh onecode config`, `cdh onecode codebase`, `cdh onecode skill`.
|
|
50
60
|
"""
|
|
51
61
|
if ctx.invoked_subcommand is None:
|
|
52
62
|
ensure_dirs()
|
|
@@ -56,55 +66,214 @@ def cli(ctx):
|
|
|
56
66
|
A2TUIApp().run()
|
|
57
67
|
|
|
58
68
|
|
|
59
|
-
# ---
|
|
69
|
+
# --- onecode sub-namespace ---
|
|
70
|
+
#
|
|
71
|
+
# `cdh onecode <sub>` exposes the onecode CLI surface under the cdh
|
|
72
|
+
# namespace. The onecode group owns the canonical subcommands
|
|
73
|
+
# (config / codebase / skill / mcp / help); running
|
|
74
|
+
# `cdh onecode config` with no subcommand opens the same interactive
|
|
75
|
+
# TUI editor as before, while `cdh onecode config <sub>` dispatches
|
|
76
|
+
# straight to the onecode implementation.
|
|
60
77
|
|
|
61
78
|
@cli.group(
|
|
79
|
+
"onecode",
|
|
62
80
|
invoke_without_command=True,
|
|
63
|
-
short_help="
|
|
81
|
+
short_help="onecode CLI surface (config / codebase / skill / mcp / help)",
|
|
64
82
|
)
|
|
65
83
|
@click.pass_context
|
|
66
|
-
def
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
Launches a Textual-based UI for editing all CDH settings
|
|
70
|
-
including providers, models, cloud platforms, agent parameters.
|
|
71
|
-
"""
|
|
72
|
-
if ctx.invoked_subcommand is None:
|
|
73
|
-
from onecode.config_screen import main as config_main
|
|
74
|
-
config_main()
|
|
84
|
+
def onecode_group(ctx):
|
|
85
|
+
"""The onecode command surface.
|
|
75
86
|
|
|
87
|
+
onecode is the agent framework that powers the TUI. This group
|
|
88
|
+
exposes its full CLI: configuration, codebase indexing, skills,
|
|
89
|
+
MCP servers, and help.
|
|
76
90
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
\b
|
|
92
|
+
Sub-commands:
|
|
93
|
+
config Manage onecode configuration (mode, model, provider, log-level, list)
|
|
94
|
+
codebase Build, search, and inspect the onecode codebase index
|
|
95
|
+
skill Install, enable, disable, or remove onecode skills
|
|
96
|
+
mcp Configure MCP servers that onecode connects to at runtime
|
|
97
|
+
help Print the onecode help screen
|
|
98
|
+
version Print the onecode version string
|
|
81
99
|
|
|
100
|
+
\b
|
|
101
|
+
Examples:
|
|
102
|
+
cdh onecode config Open the onecode configuration editor
|
|
103
|
+
cdh onecode config model get Show the current default model
|
|
104
|
+
cdh onecode config list Dump the full YAML configuration
|
|
105
|
+
cdh onecode config provider set openai
|
|
106
|
+
cdh onecode codebase index Build the onecode codebase index
|
|
107
|
+
cdh onecode codebase search "auth flow"
|
|
108
|
+
cdh onecode skill list List installed onecode skills
|
|
109
|
+
cdh onecode skill add my-skill Scaffold a new onecode skill
|
|
110
|
+
cdh onecode mcp list List onecode MCP servers
|
|
111
|
+
cdh onecode mcp add my-server https://example.com/mcp
|
|
112
|
+
"""
|
|
113
|
+
if ctx.invoked_subcommand is None:
|
|
114
|
+
# No subcommand: show the onecode help screen (matches
|
|
115
|
+
# `onecode --help` so the user can browse what's available).
|
|
116
|
+
click.echo(onecode_cli.get_help(click.Context(onecode_cli)))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Mount every onecode top-level command (codebase / skill / mcp /
|
|
120
|
+
# help / version) under `cdh onecode <sub>`. We deliberately skip
|
|
121
|
+
# `config` because the onecode CLI's bare `onecode config` only prints
|
|
122
|
+
# `--help`; the user-facing behaviour for `cdh onecode config` is to
|
|
123
|
+
# open the TUI editor, so we install our own config sub-group below.
|
|
124
|
+
_ONECODE_SKIP_AS_CDH_ONECODE = {"tui", "config"}
|
|
125
|
+
for _cmd_name in onecode_cli.commands:
|
|
126
|
+
if _cmd_name in _ONECODE_SKIP_AS_CDH_ONECODE:
|
|
127
|
+
continue
|
|
128
|
+
_cmd = onecode_cli.get_command(None, _cmd_name)
|
|
129
|
+
if _cmd is not None and _cmd_name not in onecode_group.commands:
|
|
130
|
+
onecode_group.add_command(_cmd)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Override the onecode CLI's CDH-flavoured help text so users see
|
|
134
|
+
# onecode-centric descriptions when they run `cdh onecode --help` or
|
|
135
|
+
# `cdh help onecode`. The onecode CLI itself still ships its own copy
|
|
136
|
+
# (used by `onecode --help`); we don't mutate it.
|
|
137
|
+
|
|
138
|
+
_ONECODE_HELP_OVERRIDES: dict[str, tuple[str, str]] = {
|
|
139
|
+
"config": (
|
|
140
|
+
"Open the onecode configuration editor",
|
|
141
|
+
"Manage onecode configuration: agent mode, default model, LLM "
|
|
142
|
+
"provider, log level, skills, MCP servers. With no subcommand, "
|
|
143
|
+
"launches the interactive Textual editor.",
|
|
144
|
+
),
|
|
145
|
+
"codebase": (
|
|
146
|
+
"Manage the onecode codebase index",
|
|
147
|
+
"Build and query onecode's local codebase index. Used by the "
|
|
148
|
+
"agent to ground answers in the project's own source files. "
|
|
149
|
+
"Sub-commands: index, reindex, status, search.",
|
|
150
|
+
),
|
|
151
|
+
"skill": (
|
|
152
|
+
"Manage onecode skills",
|
|
153
|
+
"Install, enable, disable, or remove onecode skills. Skills are "
|
|
154
|
+
"reusable instruction sets the agent loads on demand to extend "
|
|
155
|
+
"its behaviour. Sub-commands: list, add, remove, enable, disable.",
|
|
156
|
+
),
|
|
157
|
+
"mcp": (
|
|
158
|
+
"Manage onecode MCP servers",
|
|
159
|
+
"Configure Model Context Protocol servers that onecode connects "
|
|
160
|
+
"to at runtime to expose additional tools and resources. "
|
|
161
|
+
"Sub-commands: list, add, remove, enable, disable.",
|
|
162
|
+
),
|
|
163
|
+
"help": (
|
|
164
|
+
"Show onecode CLI help",
|
|
165
|
+
"Print the onecode CLI help screen with every available command.",
|
|
166
|
+
),
|
|
167
|
+
"version": (
|
|
168
|
+
"Show onecode version",
|
|
169
|
+
"Print the onecode version string.",
|
|
170
|
+
),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for _cmd_name, (_sh, _doc) in _ONECODE_HELP_OVERRIDES.items():
|
|
174
|
+
_mounted = onecode_group.commands.get(_cmd_name)
|
|
175
|
+
if _mounted is not None:
|
|
176
|
+
_mounted.short_help = _sh
|
|
177
|
+
_mounted.help = _doc
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# `cdh onecode config` — wrapper around `onecode config` that opens the
|
|
181
|
+
# interactive TUI editor when invoked with no subcommand, and forwards
|
|
182
|
+
# every subcommand (mode / model / provider / skill / mcp / log-level /
|
|
183
|
+
# list) verbatim to onecode.
|
|
184
|
+
|
|
185
|
+
@onecode_group.group(
|
|
186
|
+
"config",
|
|
187
|
+
invoke_without_command=True,
|
|
188
|
+
short_help="Open the onecode configuration editor (or run `cdh onecode config <sub>`)",
|
|
189
|
+
)
|
|
190
|
+
@click.pass_context
|
|
191
|
+
def onecode_config(ctx):
|
|
192
|
+
"""Open the onecode configuration editor.
|
|
82
193
|
|
|
83
|
-
|
|
194
|
+
With a subcommand, get or set the corresponding onecode setting
|
|
195
|
+
directly from the shell. With no subcommand, launch the interactive
|
|
196
|
+
Textual editor for all onecode settings.
|
|
84
197
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
198
|
+
\b
|
|
199
|
+
Sub-commands:
|
|
200
|
+
mode Get or set onecode's default agent mode (build / plan / solo)
|
|
201
|
+
model Get or set onecode's default LLM model
|
|
202
|
+
provider Get or set onecode's default LLM provider
|
|
203
|
+
log-level Get or set onecode's root logger verbosity
|
|
204
|
+
skill Manage onecode skills (alias for `cdh onecode skill`)
|
|
205
|
+
mcp Manage onecode MCP servers (alias for `cdh onecode mcp`)
|
|
206
|
+
list Dump the full onecode YAML configuration
|
|
90
207
|
|
|
91
208
|
\b
|
|
92
209
|
Examples:
|
|
93
|
-
cdh
|
|
94
|
-
cdh
|
|
95
|
-
cdh
|
|
210
|
+
cdh onecode config Open the interactive editor
|
|
211
|
+
cdh onecode config model get Show the current default model
|
|
212
|
+
cdh onecode config provider set openai
|
|
213
|
+
cdh onecode config list Dump the full YAML configuration
|
|
96
214
|
"""
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
215
|
+
if ctx.invoked_subcommand is None:
|
|
216
|
+
from onecode.config_screen import main as config_main
|
|
217
|
+
config_main()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
_onecode_config_group = onecode_cli.get_command(None, "config")
|
|
221
|
+
if _onecode_config_group is not None:
|
|
222
|
+
for _cfg_cmd in _onecode_config_group.commands.values():
|
|
223
|
+
if _cfg_cmd.name not in onecode_config.commands and _cfg_cmd.name != "tui":
|
|
224
|
+
onecode_config.add_command(_cfg_cmd)
|
|
225
|
+
# Override the sub-command help to be onecode-centric, not
|
|
226
|
+
# CDH-centric, since users reach these via
|
|
227
|
+
# `cdh onecode config <sub>`.
|
|
228
|
+
_sub_overrides = {
|
|
229
|
+
"mode": (
|
|
230
|
+
"Manage onecode agent mode",
|
|
231
|
+
"Get or set onecode's default agent mode "
|
|
232
|
+
"(build / plan / solo).",
|
|
233
|
+
),
|
|
234
|
+
"model": (
|
|
235
|
+
"Manage onecode's default LLM model",
|
|
236
|
+
"Get or set the default LLM model onecode uses when "
|
|
237
|
+
"starting a new agent session.",
|
|
238
|
+
),
|
|
239
|
+
"provider": (
|
|
240
|
+
"Manage onecode's default LLM provider",
|
|
241
|
+
"Get or set the default LLM provider onecode routes "
|
|
242
|
+
"chat-completions requests to.",
|
|
243
|
+
),
|
|
244
|
+
"log-level": (
|
|
245
|
+
"Manage onecode log level",
|
|
246
|
+
"Get or set onecode's root logger verbosity "
|
|
247
|
+
"(debug / info / warn / error).",
|
|
248
|
+
),
|
|
249
|
+
"skill": (
|
|
250
|
+
"Manage onecode skills (alias for `cdh onecode skill`)",
|
|
251
|
+
"Same skill-management surface as `cdh onecode skill`.",
|
|
252
|
+
),
|
|
253
|
+
"mcp": (
|
|
254
|
+
"Manage onecode MCP servers (alias for `cdh onecode mcp`)",
|
|
255
|
+
"Same MCP server surface as `cdh onecode mcp`.",
|
|
256
|
+
),
|
|
257
|
+
"list": (
|
|
258
|
+
"Show onecode's full YAML configuration",
|
|
259
|
+
"Print every onecode setting currently in effect, "
|
|
260
|
+
"as YAML.",
|
|
261
|
+
),
|
|
262
|
+
}
|
|
263
|
+
if _cfg_cmd.name in _sub_overrides:
|
|
264
|
+
_sh, _doc = _sub_overrides[_cfg_cmd.name]
|
|
265
|
+
_cfg_cmd.short_help = _sh
|
|
266
|
+
_cfg_cmd.help = _doc
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# --- (removed) backward-compatible aliases ---
|
|
270
|
+
#
|
|
271
|
+
# Earlier versions exposed `cdh config` and `cdh codebase` at the top
|
|
272
|
+
# level. Those aliases were removed — use `cdh onecode config` and
|
|
273
|
+
# `cdh onecode codebase` instead. The behaviour is identical:
|
|
274
|
+
# - `cdh onecode config` (no subcommand) opens the same TUI editor.
|
|
275
|
+
# - `cdh onecode config <sub>` is the same as the old `cdh config <sub>`.
|
|
276
|
+
# - `cdh onecode codebase <sub>` is the same as the old `cdh codebase <sub>`.
|
|
108
277
|
|
|
109
278
|
|
|
110
279
|
# --- project command ---
|
|
@@ -143,11 +312,133 @@ def _interactive_select_project():
|
|
|
143
312
|
return None
|
|
144
313
|
|
|
145
314
|
|
|
315
|
+
def _parse_component_selection(text: str, valid_ids: list[str]) -> list[str]:
|
|
316
|
+
"""Parse '1,2,5' / '1-3' / 'all' / 'web,backend' / '' into a list of ids.
|
|
317
|
+
|
|
318
|
+
Accepts either numeric tokens (positional, 1-based) or raw id names.
|
|
319
|
+
Returns an empty list if no valid tokens are found.
|
|
320
|
+
"""
|
|
321
|
+
text = text.strip().lower()
|
|
322
|
+
if not text:
|
|
323
|
+
return []
|
|
324
|
+
if text in ("all", "*"):
|
|
325
|
+
return list(valid_ids)
|
|
326
|
+
tokens = [t.strip() for t in text.replace(" ", "").split(",") if t.strip()]
|
|
327
|
+
selected: list[str] = []
|
|
328
|
+
for tok in tokens:
|
|
329
|
+
if tok in valid_ids:
|
|
330
|
+
if tok not in selected:
|
|
331
|
+
selected.append(tok)
|
|
332
|
+
continue
|
|
333
|
+
if "-" in tok:
|
|
334
|
+
try:
|
|
335
|
+
a, b = tok.split("-", 1)
|
|
336
|
+
lo, hi = int(a), int(b)
|
|
337
|
+
except ValueError:
|
|
338
|
+
continue
|
|
339
|
+
if lo > hi:
|
|
340
|
+
lo, hi = hi, lo
|
|
341
|
+
for i in range(lo, hi + 1):
|
|
342
|
+
if 1 <= i <= len(valid_ids) and valid_ids[i - 1] not in selected:
|
|
343
|
+
selected.append(valid_ids[i - 1])
|
|
344
|
+
else:
|
|
345
|
+
try:
|
|
346
|
+
idx = int(tok)
|
|
347
|
+
except ValueError:
|
|
348
|
+
continue
|
|
349
|
+
if 1 <= idx <= len(valid_ids) and valid_ids[idx - 1] not in selected:
|
|
350
|
+
selected.append(valid_ids[idx - 1])
|
|
351
|
+
return selected
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _prompt_components(
|
|
355
|
+
spec_label: str,
|
|
356
|
+
specs: tuple,
|
|
357
|
+
allow_empty: bool = False,
|
|
358
|
+
) -> list[str]:
|
|
359
|
+
"""Interactively prompt the user to choose from a list of specs.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
spec_label: title describing what is being selected
|
|
363
|
+
(e.g. "application components").
|
|
364
|
+
specs: a tuple of ComponentSpec or CrossCutSpec.
|
|
365
|
+
allow_empty: if False, the user must select at least one and
|
|
366
|
+
the prompt will reject empty input.
|
|
367
|
+
|
|
368
|
+
Returns a list of selected spec ids. Raises click.Abort if the user
|
|
369
|
+
fails to provide a valid selection after the retry.
|
|
370
|
+
"""
|
|
371
|
+
valid_ids = [s.id for s in specs]
|
|
372
|
+
click.echo(f"Select {spec_label} for the project:")
|
|
373
|
+
for i, s in enumerate(specs, 1):
|
|
374
|
+
click.echo(f" {i}) {s.label:<22s} \u2014 {s.description}")
|
|
375
|
+
if allow_empty:
|
|
376
|
+
hint = "[optional, e.g. 1,2 or 'all' or <Enter> to skip]"
|
|
377
|
+
else:
|
|
378
|
+
hint = "[required, e.g. 1,2 or 'all']"
|
|
379
|
+
click.echo("")
|
|
380
|
+
prompt_text = f"{spec_label.capitalize()} {hint}: "
|
|
381
|
+
|
|
382
|
+
for attempt in range(2):
|
|
383
|
+
raw = click.prompt(prompt_text, default="", show_default=False)
|
|
384
|
+
selected = _parse_component_selection(raw, valid_ids)
|
|
385
|
+
if selected:
|
|
386
|
+
return selected
|
|
387
|
+
if allow_empty and not raw.strip():
|
|
388
|
+
return []
|
|
389
|
+
if attempt == 0:
|
|
390
|
+
if allow_empty:
|
|
391
|
+
click.echo(
|
|
392
|
+
"No valid selection parsed. Try again (or press Enter to skip)."
|
|
393
|
+
)
|
|
394
|
+
else:
|
|
395
|
+
click.echo(
|
|
396
|
+
"At least one selection is required. Try again."
|
|
397
|
+
)
|
|
398
|
+
raise click.Abort()
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _resolve_components_flag(components: Optional[str]) -> list[str]:
|
|
402
|
+
"""Resolve a --components flag value to a list of component ids.
|
|
403
|
+
|
|
404
|
+
Empty/None means the caller will run the interactive prompt.
|
|
405
|
+
'all' expands to every component id.
|
|
406
|
+
"""
|
|
407
|
+
if not components:
|
|
408
|
+
return []
|
|
409
|
+
valid_ids = [c.id for c in COMPONENTS]
|
|
410
|
+
selected = _parse_component_selection(components, valid_ids)
|
|
411
|
+
if not selected:
|
|
412
|
+
raise click.BadParameter(
|
|
413
|
+
f"No valid component ids in '{components}'. "
|
|
414
|
+
f"Valid ids: {', '.join(valid_ids)}."
|
|
415
|
+
)
|
|
416
|
+
return selected
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _load_project_record(name: str) -> Optional[dict]:
|
|
420
|
+
"""Read a project record from ~/.cdh/projects/{name}.{yaml,yml,json}."""
|
|
421
|
+
import yaml as _yaml
|
|
422
|
+
import json as _json
|
|
423
|
+
projects_dir = _CDH_DIR / "projects"
|
|
424
|
+
for ext in ("yaml", "yml", "json"):
|
|
425
|
+
pf = projects_dir / f"{name}.{ext}"
|
|
426
|
+
if pf.exists():
|
|
427
|
+
text = pf.read_text(encoding="utf-8")
|
|
428
|
+
if ext in ("yaml", "yml"):
|
|
429
|
+
return _yaml.safe_load(text) or {}
|
|
430
|
+
return _json.loads(text)
|
|
431
|
+
return None
|
|
432
|
+
|
|
433
|
+
|
|
146
434
|
@cli.command(short_help="Project management (TUI)")
|
|
147
|
-
@click.argument("action", type=click.Choice(["list", "show", "new", "init", "load", "select"]), default="select")
|
|
435
|
+
@click.argument("action", type=click.Choice(["list", "show", "new", "init", "load", "select", "add-component", "add-cross-cutting"]), default="select")
|
|
148
436
|
@click.argument("name", required=False)
|
|
149
437
|
@click.argument("path", required=False, default=".")
|
|
150
|
-
|
|
438
|
+
@click.option("--components", default=None, help="Comma-separated component ids (e.g. 'web,backend') or 'all'. Skips the interactive prompt.")
|
|
439
|
+
@click.option("--component", "component_id", default=None, help="Component id for add-component (e.g. 'native').")
|
|
440
|
+
@click.option("--id", "cross_id", default=None, help="Cross-cutting id for add-cross-cutting (e.g. 'contracts').")
|
|
441
|
+
def project(action, name, path, components, component_id, cross_id):
|
|
151
442
|
"""Manage CDH projects.
|
|
152
443
|
|
|
153
444
|
\b
|
|
@@ -156,12 +447,14 @@ def project(action, name, path):
|
|
|
156
447
|
|
|
157
448
|
\b
|
|
158
449
|
Actions:
|
|
159
|
-
select
|
|
160
|
-
list
|
|
161
|
-
show <name>
|
|
162
|
-
new <name> [path]
|
|
163
|
-
init [path]
|
|
164
|
-
load <name>
|
|
450
|
+
select Open project management TUI (default)
|
|
451
|
+
list List all projects
|
|
452
|
+
show <name> Show project details
|
|
453
|
+
new <name> [path] Create a new project (interactive component selection)
|
|
454
|
+
init [path] Initialize .cdh in an existing directory (interactive, allows empty)
|
|
455
|
+
load <name> Load a project (set as current)
|
|
456
|
+
add-component <name> Add an application component to an existing project
|
|
457
|
+
add-cross-cutting <name> Add a cross-cutting item to an existing project
|
|
165
458
|
"""
|
|
166
459
|
import yaml
|
|
167
460
|
projects_dir = _CDH_DIR / "projects"
|
|
@@ -183,7 +476,9 @@ def project(action, name, path):
|
|
|
183
476
|
click.echo("Projects:")
|
|
184
477
|
for pf in project_files:
|
|
185
478
|
click.echo(f" {pf.stem}")
|
|
186
|
-
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
if action == "show":
|
|
187
482
|
if not name:
|
|
188
483
|
click.echo("Usage: cdh project show <name>")
|
|
189
484
|
return
|
|
@@ -193,31 +488,128 @@ def project(action, name, path):
|
|
|
193
488
|
click.echo(pf.read_text())
|
|
194
489
|
return
|
|
195
490
|
click.echo(f"Project '{name}' not found.")
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return
|
|
491
|
+
return
|
|
492
|
+
|
|
493
|
+
if action == "new":
|
|
200
494
|
from onecode.agent.cdh_loader import CdhProjectLoader
|
|
495
|
+
if not name:
|
|
496
|
+
name = click.prompt("Project name", default="").strip()
|
|
497
|
+
if not name:
|
|
498
|
+
click.echo("Project name is required.")
|
|
499
|
+
raise click.Abort()
|
|
201
500
|
ws = Path(path).expanduser().resolve()
|
|
202
|
-
|
|
501
|
+
project_file = projects_dir / f"{name}.yaml"
|
|
502
|
+
if project_file.exists():
|
|
503
|
+
click.echo(f"Error: project '{name}' already exists in the project list.")
|
|
504
|
+
raise click.Abort()
|
|
505
|
+
if components is not None:
|
|
506
|
+
selected_components = _resolve_components_flag(components)
|
|
507
|
+
else:
|
|
508
|
+
selected_components = _prompt_components(
|
|
509
|
+
"application components", COMPONENTS, allow_empty=False
|
|
510
|
+
)
|
|
511
|
+
try:
|
|
512
|
+
scaffold_dlc_project(ws, name, components=selected_components)
|
|
513
|
+
except (ValueError, RuntimeError) as e:
|
|
514
|
+
click.echo(f"Error: {str(e) or type(e).__name__}")
|
|
515
|
+
raise click.Abort()
|
|
203
516
|
CdhProjectLoader.init_project(ws, name)
|
|
204
517
|
proj_data = {"name": name, "path": str(ws), "description": ""}
|
|
205
|
-
project_file = projects_dir / f"{name}.yaml"
|
|
206
518
|
project_file.write_text(yaml.dump(proj_data))
|
|
207
519
|
cfg = load_config()
|
|
208
520
|
cfg.current_project = name
|
|
209
521
|
cfg.current_project_path = str(ws)
|
|
210
522
|
save_config(cfg)
|
|
211
|
-
click.echo(
|
|
212
|
-
|
|
523
|
+
click.echo(
|
|
524
|
+
f"Created project '{name}' at {ws} "
|
|
525
|
+
f"(components: {', '.join(selected_components)})"
|
|
526
|
+
)
|
|
527
|
+
return
|
|
528
|
+
|
|
529
|
+
if action == "init":
|
|
213
530
|
from onecode.agent.cdh_loader import CdhProjectLoader
|
|
214
|
-
from cdh.scaffold import init_dlc_project
|
|
215
531
|
target = Path(name or ".").expanduser().resolve()
|
|
216
532
|
project_name = target.name
|
|
217
|
-
|
|
533
|
+
proj_file = projects_dir / f"{project_name}.yaml"
|
|
534
|
+
if proj_file.exists():
|
|
535
|
+
click.echo(f"Error: project '{project_name}' already exists in the project list.")
|
|
536
|
+
raise click.Abort()
|
|
537
|
+
if components is not None:
|
|
538
|
+
selected_components = _resolve_components_flag(components)
|
|
539
|
+
else:
|
|
540
|
+
selected_components = _prompt_components(
|
|
541
|
+
"application components", COMPONENTS, allow_empty=True
|
|
542
|
+
)
|
|
543
|
+
try:
|
|
544
|
+
init_dlc_project(target, project_name)
|
|
545
|
+
except (ValueError, RuntimeError) as e:
|
|
546
|
+
click.echo(f"Error: {str(e) or type(e).__name__}")
|
|
547
|
+
raise click.Abort()
|
|
548
|
+
for cid in selected_components:
|
|
549
|
+
try:
|
|
550
|
+
add_component(target, cid)
|
|
551
|
+
except (ValueError, FileNotFoundError) as e:
|
|
552
|
+
click.echo(f"Error: {str(e) or type(e).__name__}")
|
|
218
553
|
CdhProjectLoader.init_project(target, project_name)
|
|
219
|
-
|
|
220
|
-
|
|
554
|
+
proj_data = {"name": project_name, "path": str(target), "description": ""}
|
|
555
|
+
proj_file.write_text(yaml.dump(proj_data))
|
|
556
|
+
suffix = (
|
|
557
|
+
f" (components: {', '.join(selected_components)})"
|
|
558
|
+
if selected_components
|
|
559
|
+
else ""
|
|
560
|
+
)
|
|
561
|
+
click.echo(f"Initialized .cdh in {target}{suffix}")
|
|
562
|
+
return
|
|
563
|
+
|
|
564
|
+
if action == "add-component":
|
|
565
|
+
if not name:
|
|
566
|
+
click.echo("Usage: cdh project add-component <name> --component <id>")
|
|
567
|
+
return
|
|
568
|
+
if not component_id:
|
|
569
|
+
click.echo("Usage: cdh project add-component <name> --component <id>")
|
|
570
|
+
click.echo(f"Valid ids: {', '.join(c.id for c in COMPONENTS)}")
|
|
571
|
+
return
|
|
572
|
+
record = _load_project_record(name)
|
|
573
|
+
if record is None:
|
|
574
|
+
click.echo(f"Project '{name}' not found.")
|
|
575
|
+
return
|
|
576
|
+
ws = Path(record.get("path", ".")).expanduser().resolve()
|
|
577
|
+
try:
|
|
578
|
+
added = add_component(ws, component_id)
|
|
579
|
+
except (ValueError, FileNotFoundError) as e:
|
|
580
|
+
click.echo(f"Error: {str(e) or type(e).__name__}")
|
|
581
|
+
raise click.Abort()
|
|
582
|
+
if added:
|
|
583
|
+
click.echo(f"Added component '{component_id}' to '{name}'.")
|
|
584
|
+
else:
|
|
585
|
+
click.echo(f"Component '{component_id}' already present in '{name}'.")
|
|
586
|
+
return
|
|
587
|
+
|
|
588
|
+
if action == "add-cross-cutting":
|
|
589
|
+
if not name:
|
|
590
|
+
click.echo("Usage: cdh project add-cross-cutting <name> --id <id>")
|
|
591
|
+
return
|
|
592
|
+
if not cross_id:
|
|
593
|
+
click.echo("Usage: cdh project add-cross-cutting <name> --id <id>")
|
|
594
|
+
click.echo(f"Valid ids: {', '.join(c.id for c in CROSS_CUTTING)}")
|
|
595
|
+
return
|
|
596
|
+
record = _load_project_record(name)
|
|
597
|
+
if record is None:
|
|
598
|
+
click.echo(f"Project '{name}' not found.")
|
|
599
|
+
return
|
|
600
|
+
ws = Path(record.get("path", ".")).expanduser().resolve()
|
|
601
|
+
try:
|
|
602
|
+
added = add_cross_cutting(ws, cross_id)
|
|
603
|
+
except (ValueError, FileNotFoundError) as e:
|
|
604
|
+
click.echo(f"Error: {str(e) or type(e).__name__}")
|
|
605
|
+
raise click.Abort()
|
|
606
|
+
if added:
|
|
607
|
+
click.echo(f"Added cross-cutting '{cross_id}' to '{name}'.")
|
|
608
|
+
else:
|
|
609
|
+
click.echo(f"Cross-cutting '{cross_id}' already present in '{name}'.")
|
|
610
|
+
return
|
|
611
|
+
|
|
612
|
+
if action == "load":
|
|
221
613
|
if not name:
|
|
222
614
|
click.echo("Usage: cdh project load <name>")
|
|
223
615
|
return
|
|
@@ -291,13 +683,12 @@ def session(action, session_id):
|
|
|
291
683
|
|
|
292
684
|
@cli.command(short_help="Launch the TUI")
|
|
293
685
|
@click.option("--project-dir", "-d", default=".", help="Project directory")
|
|
294
|
-
@click.option("--agent", "agent_identity", default=None, help="Agent identity to auto-launch (e.g. cdh.cloud-dev-harness)")
|
|
295
686
|
@click.pass_context
|
|
296
|
-
def tui(ctx, project_dir
|
|
687
|
+
def tui(ctx, project_dir):
|
|
297
688
|
"""Launch the CDH TUI (Textual User Interface).
|
|
298
689
|
|
|
299
690
|
By default opens the agent store, showing your configured launcher
|
|
300
|
-
agents.
|
|
691
|
+
agents. Pick an agent from the store to launch it.
|
|
301
692
|
"""
|
|
302
693
|
ensure_dirs()
|
|
303
694
|
cfg = load_config()
|
|
@@ -306,10 +697,7 @@ def tui(ctx, project_dir, agent_identity):
|
|
|
306
697
|
ws = Path(project_dir).expanduser().resolve()
|
|
307
698
|
|
|
308
699
|
from tui.app import A2TUIApp
|
|
309
|
-
app = A2TUIApp(
|
|
310
|
-
project_dir=str(ws),
|
|
311
|
-
launch_agent_identity=agent_identity,
|
|
312
|
-
)
|
|
700
|
+
app = A2TUIApp(project_dir=str(ws))
|
|
313
701
|
app.run()
|
|
314
702
|
|
|
315
703
|
|
|
@@ -322,9 +710,9 @@ def help_cmd(command):
|
|
|
322
710
|
|
|
323
711
|
\b
|
|
324
712
|
Examples:
|
|
325
|
-
cdh help
|
|
326
|
-
cdh help
|
|
327
|
-
cdh help
|
|
713
|
+
cdh help Show top-level help
|
|
714
|
+
cdh help onecode Show `cdh onecode` sub-commands
|
|
715
|
+
cdh help onecode config Show config sub-commands
|
|
328
716
|
"""
|
|
329
717
|
if command:
|
|
330
718
|
cmd = cli.commands.get(command)
|
|
@@ -345,12 +733,10 @@ def version():
|
|
|
345
733
|
click.echo(f"cdh version {__version__}")
|
|
346
734
|
|
|
347
735
|
|
|
348
|
-
#
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
continue
|
|
353
|
-
cli.add_command(onecode_cli.get_command(None, cmd_name))
|
|
736
|
+
# All onecode subcommands are reachable through `cdh onecode <sub>`
|
|
737
|
+
# (see the onecode_group defined above). The cdh top-level command
|
|
738
|
+
# surface is intentionally limited to: tui, onecode, project, session,
|
|
739
|
+
# help, and version.
|
|
354
740
|
|
|
355
741
|
|
|
356
742
|
def main():
|