@scemoon/cdh 1.0.0 → 1.0.4
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__/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/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 +202 -75
- package/onecode/agent/session.py +8 -13
- package/onecode/agent/tools/__pycache__/agent_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/tasks/manager.py +1 -1
- package/package.json +1 -1
- package/pyproject.toml +10 -0
- package/run.js +24 -7
- package/tui/__pycache__/agents.cpython-314.pyc +0 -0
- package/tui/__pycache__/app.cpython-314.pyc +0 -0
- package/tui/__pycache__/db.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/__pycache__/settings_schema.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/agents.py +8 -7
- package/tui/app.py +60 -8
- package/tui/data/agents/ampcode.com.toml +1 -0
- package/tui/data/agents/augmentcode.com.toml +1 -0
- package/tui/data/agents/cdh.cloud-dev-harness.toml +3 -2
- package/tui/data/agents/crowai.dev.toml +1 -0
- package/tui/data/agents/deepagents.langchain.com +1 -0
- package/tui/data/agents/docker.com.toml +1 -0
- package/tui/data/agents/goose.ai.toml +1 -0
- package/tui/data/agents/inference.huggingface.co.toml +1 -1
- package/tui/data/agents/kiro.dev.toml +1 -0
- package/tui/data/agents/onecode.dev.toml +22 -0
- package/tui/data/agents/openclaw.ai.toml +1 -1
- package/tui/data/agents/openhands.dev.toml +1 -0
- package/tui/data/agents/stakpak.dev.toml +1 -0
- package/tui/data/agents/vibe.mistral.ai.toml +1 -0
- package/tui/data/agents/vtcode.dev.toml +1 -0
- 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__/main.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/screens/store.py +1 -1
- package/tui/settings_schema.py +1 -1
- 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__/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__/question.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent.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 +191 -65
- 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/cdh/__pycache__/__init__.cpython-313.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-313.pyc +0 -0
- package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
- package/onecode/__pycache__/mock_data.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_agent_acp.cpython-313.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_agent_acp.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/harness_skill.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/task_tools.py +0 -249
- package/onecode/codebase/__pycache__/config.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/mock_provider.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
- package/tui/__pycache__/danger.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/sessions.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/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/conversation.cpython-313.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__/future_text.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/terminal.cpython-313.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-313.pyc +0 -0
package/tui/app.py
CHANGED
|
@@ -363,6 +363,7 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
363
363
|
session_id = getattr(conv.agent, "session_id", None) or conv._agent_session_id or ""
|
|
364
364
|
self._exit_metrics = {
|
|
365
365
|
"session_id": session_id,
|
|
366
|
+
"session_pk": conv._session_pk,
|
|
366
367
|
"tool_call_total": conv._tool_call_total,
|
|
367
368
|
"tool_call_success": conv._tool_call_success,
|
|
368
369
|
"tool_call_failed": conv._tool_call_failed,
|
|
@@ -876,11 +877,16 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
876
877
|
)
|
|
877
878
|
return
|
|
878
879
|
if agent_identity:
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
880
|
+
try:
|
|
881
|
+
from tui.agents import read_agents
|
|
882
|
+
if agent_identity in await read_agents():
|
|
883
|
+
self.launch_agent(
|
|
884
|
+
agent_identity,
|
|
885
|
+
agent_session_id=agent_session_id,
|
|
886
|
+
)
|
|
887
|
+
return
|
|
888
|
+
except Exception:
|
|
889
|
+
pass
|
|
884
890
|
|
|
885
891
|
if agent_identity := self._resolve_launch_agent():
|
|
886
892
|
self.launch_agent(agent_identity, project_path=project_path)
|
|
@@ -999,9 +1005,27 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
999
1005
|
except Exception:
|
|
1000
1006
|
pass
|
|
1001
1007
|
sid = (self._exit_metrics.get("session_id", "") or "")[:36] if self._exit_metrics else ""
|
|
1008
|
+
session_pk = self._exit_metrics.get("session_pk") if self._exit_metrics else None
|
|
1009
|
+
# Format the session ID for display: prefer the short `cdh
|
|
1010
|
+
# session list` form (`session-<pk>` + agent_session_id prefix)
|
|
1011
|
+
# so the displayed value matches what users see in
|
|
1012
|
+
# `cdh session list` and what they pass to `cdh session load`.
|
|
1013
|
+
if session_pk is not None:
|
|
1014
|
+
display_id = f"session-{session_pk} ({sid[:8]})" if sid else f"session-{session_pk}"
|
|
1015
|
+
hint = f" Reload with: cdh session load {session_pk}\n"
|
|
1016
|
+
elif sid:
|
|
1017
|
+
display_id = sid
|
|
1018
|
+
hint = ""
|
|
1019
|
+
else:
|
|
1020
|
+
display_id = "-"
|
|
1021
|
+
hint = ""
|
|
1002
1022
|
try:
|
|
1003
1023
|
with open("/dev/tty", "w") as tty:
|
|
1004
|
-
tty.write(
|
|
1024
|
+
tty.write(
|
|
1025
|
+
f"\n CDH powering down. Goodbye!\n"
|
|
1026
|
+
f" Session ID: {display_id}\n"
|
|
1027
|
+
f"{hint}"
|
|
1028
|
+
)
|
|
1005
1029
|
except Exception:
|
|
1006
1030
|
pass
|
|
1007
1031
|
import os
|
|
@@ -1079,10 +1103,23 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
1079
1103
|
return
|
|
1080
1104
|
agent_identity = session["agent_identity"]
|
|
1081
1105
|
agent_session_id = session.get("agent_session_id")
|
|
1106
|
+
|
|
1107
|
+
project_path = None
|
|
1108
|
+
try:
|
|
1109
|
+
meta = json.loads(session.get("meta_json", "{}"))
|
|
1110
|
+
if cwd := meta.get("cwd"):
|
|
1111
|
+
project_path = Path(cwd)
|
|
1112
|
+
except (json.JSONDecodeError, TypeError):
|
|
1113
|
+
pass
|
|
1114
|
+
|
|
1115
|
+
if project_path:
|
|
1116
|
+
self.project_dir = project_path
|
|
1117
|
+
|
|
1082
1118
|
self.launch_agent(
|
|
1083
1119
|
agent_identity,
|
|
1084
1120
|
agent_session_id=agent_session_id,
|
|
1085
1121
|
session_pk=session_pk,
|
|
1122
|
+
project_path=project_path,
|
|
1086
1123
|
)
|
|
1087
1124
|
|
|
1088
1125
|
@work
|
|
@@ -1110,6 +1147,17 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
1110
1147
|
import yaml
|
|
1111
1148
|
|
|
1112
1149
|
result = await self.push_screen_wait("projects")
|
|
1150
|
+
if result is None:
|
|
1151
|
+
cfg = load_config()
|
|
1152
|
+
if cfg.current_project:
|
|
1153
|
+
projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
|
|
1154
|
+
pf = projects_dir / f"{cfg.current_project}.yaml"
|
|
1155
|
+
if not pf.exists():
|
|
1156
|
+
cfg.current_project = None
|
|
1157
|
+
cfg.current_project_path = None
|
|
1158
|
+
save_config(cfg)
|
|
1159
|
+
self.project_dir = None
|
|
1160
|
+
self.screen.post_message(messages.ProjectDirectoryUpdated(project_dir=None))
|
|
1113
1161
|
if result is not None:
|
|
1114
1162
|
if result == "__new__":
|
|
1115
1163
|
default_path = str((self.project_dir or Path.cwd()).resolve())
|
|
@@ -1137,7 +1185,7 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
1137
1185
|
cfg.current_project_path = str(project_path)
|
|
1138
1186
|
save_config(cfg)
|
|
1139
1187
|
self.project_dir = project_path
|
|
1140
|
-
self.screen.post_message(messages.ProjectDirectoryUpdated())
|
|
1188
|
+
self.screen.post_message(messages.ProjectDirectoryUpdated(project_dir=self.project_dir))
|
|
1141
1189
|
self.notify(f"Created project '{name}' at {project_path}")
|
|
1142
1190
|
return
|
|
1143
1191
|
|
|
@@ -1159,8 +1207,12 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
1159
1207
|
|
|
1160
1208
|
new_project_dir = Path(project_path) if project_path else Path.cwd()
|
|
1161
1209
|
self.project_dir = new_project_dir
|
|
1162
|
-
self.screen.post_message(messages.ProjectDirectoryUpdated())
|
|
1210
|
+
self.screen.post_message(messages.ProjectDirectoryUpdated(project_dir=self.project_dir))
|
|
1163
1211
|
self.notify(f"Switched to project: {project_name}")
|
|
1212
|
+
from tui.agents import read_agents
|
|
1213
|
+
agents = await read_agents()
|
|
1214
|
+
if cfg.default_mode in agents:
|
|
1215
|
+
self.launch_agent(cfg.default_mode, project_path=new_project_dir)
|
|
1164
1216
|
|
|
1165
1217
|
@on(messages.LaunchAgent)
|
|
1166
1218
|
def on_launch_agent(self, message: messages.LaunchAgent) -> None:
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# CDH (Cloud Dev Harness) - Agent for A2TUI
|
|
2
2
|
# https://github.com/cloud-dev-harness/cdh
|
|
3
|
+
active = false
|
|
3
4
|
|
|
4
5
|
identity = "cdh.cloud-dev-harness"
|
|
5
|
-
name = "
|
|
6
|
+
name = "OneCode (legacy)"
|
|
6
7
|
short_name = "cdh"
|
|
7
8
|
url = "https://github.com/cloud-dev-harness/cdh"
|
|
8
9
|
protocol = "acp"
|
|
@@ -11,7 +12,7 @@ author_url = "https://github.com/cloud-dev-harness"
|
|
|
11
12
|
publisher_name = "CDH Team"
|
|
12
13
|
publisher_url = "https://github.com/cloud-dev-harness"
|
|
13
14
|
type = "coding"
|
|
14
|
-
description = "
|
|
15
|
+
description = "OneCode - cloud-native development Agent framework with LLM provider integration"
|
|
15
16
|
tags = []
|
|
16
17
|
recommended = true
|
|
17
18
|
help = "CDH Agent - Cloud Dev Harness\n\nA cloud-native development Agent framework with LLM provider integration.\n\nSupported providers: MiniMax, Anthropic, OpenAI, DeepSeek, GLM, Ollama"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# OneCode Agent for A2TUI
|
|
2
|
+
# https://github.com/cloud-dev-harness/cdh
|
|
3
|
+
|
|
4
|
+
identity = "onecode.dev"
|
|
5
|
+
name = "OneCode"
|
|
6
|
+
short_name = "onecode"
|
|
7
|
+
url = "https://github.com/cloud-dev-harness/cdh"
|
|
8
|
+
protocol = "acp"
|
|
9
|
+
author_name = "OneCode AI"
|
|
10
|
+
author_url = "https://github.com/cloud-dev-harness"
|
|
11
|
+
publisher_name = "onecode"
|
|
12
|
+
publisher_url = "https://github.com/cloud-dev-harness"
|
|
13
|
+
type = "coding"
|
|
14
|
+
description = "OneCode AI - terminal coding agent with multi-LLM provider integration"
|
|
15
|
+
tags = []
|
|
16
|
+
recommended = true
|
|
17
|
+
help = "OneCode AI Agent with LLM provider integration.\n\nSupported providers: MiniMax, Anthropic, OpenAI, DeepSeek, GLM, Ollama"
|
|
18
|
+
commands_module = "onecode.commands"
|
|
19
|
+
commands_prefix = "onecode:"
|
|
20
|
+
run_command."*" = "python3 -m onecode.agent.onecode_agent_acp"
|
|
21
|
+
|
|
22
|
+
[actions]
|
package/tui/db.py
CHANGED
|
@@ -172,6 +172,29 @@ class DB:
|
|
|
172
172
|
except aiosqlite.Error:
|
|
173
173
|
return False
|
|
174
174
|
|
|
175
|
+
async def session_get_by_agent_session_id(self, agent_session_id: str) -> Session | None:
|
|
176
|
+
"""Get a session from its agent_session_id.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
agent_session_id: The agent's session id (UUID).
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
A Session if one is found, or `None`.
|
|
183
|
+
"""
|
|
184
|
+
try:
|
|
185
|
+
async with self.open() as db:
|
|
186
|
+
db.row_factory = aiosqlite.Row
|
|
187
|
+
cursor = await db.execute(
|
|
188
|
+
"SELECT * from sessions WHERE agent_session_id = ?",
|
|
189
|
+
(agent_session_id,),
|
|
190
|
+
)
|
|
191
|
+
row = await cursor.fetchone()
|
|
192
|
+
except aiosqlite.Error:
|
|
193
|
+
return None
|
|
194
|
+
if row is None:
|
|
195
|
+
return None
|
|
196
|
+
return cast(Session, dict(row))
|
|
197
|
+
|
|
175
198
|
async def session_get_recent(self, max_results: int = 100) -> list[Session] | None:
|
|
176
199
|
"""Get the most recent sessions."""
|
|
177
200
|
try:
|
package/tui/directory_watcher.py
CHANGED
|
@@ -28,6 +28,32 @@ class DirectoryChanged(Message):
|
|
|
28
28
|
return isinstance(message, DirectoryChanged)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
_NOISY_DIR_NAMES = frozenset(
|
|
32
|
+
{
|
|
33
|
+
".venv",
|
|
34
|
+
".git",
|
|
35
|
+
".pytest_cache",
|
|
36
|
+
"__pycache__",
|
|
37
|
+
".mypy_cache",
|
|
38
|
+
"node_modules",
|
|
39
|
+
".uv-cache",
|
|
40
|
+
"dist",
|
|
41
|
+
"build",
|
|
42
|
+
".idea",
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _is_noisy_path(path: str) -> bool:
|
|
48
|
+
"""Return True when *path* lives inside a directory we want to
|
|
49
|
+
ignore for change notifications (virtualenvs, caches, build output…).
|
|
50
|
+
"""
|
|
51
|
+
if not path:
|
|
52
|
+
return False
|
|
53
|
+
parts = path.replace("\\", "/").split("/")
|
|
54
|
+
return any(part in _NOISY_DIR_NAMES for part in parts)
|
|
55
|
+
|
|
56
|
+
|
|
31
57
|
class _PathEventDispatcher(FileSystemEventHandler):
|
|
32
58
|
"""Dispatches file system events to multiple DirectoryWatcher instances."""
|
|
33
59
|
|
|
@@ -53,7 +79,15 @@ class _PathEventDispatcher(FileSystemEventHandler):
|
|
|
53
79
|
return bool(self._watchers)
|
|
54
80
|
|
|
55
81
|
def on_any_event(self, event: FileSystemEvent) -> None:
|
|
56
|
-
"""Dispatch events to all registered watchers.
|
|
82
|
+
"""Dispatch events to all registered watchers.
|
|
83
|
+
|
|
84
|
+
Events for paths inside noisy directories (virtualenvs, caches,
|
|
85
|
+
build output, etc.) are dropped so they don't flood the UI.
|
|
86
|
+
"""
|
|
87
|
+
src = getattr(event, "src_path", "") or ""
|
|
88
|
+
dest = getattr(event, "dest_path", "") or ""
|
|
89
|
+
if _is_noisy_path(src) or _is_noisy_path(dest):
|
|
90
|
+
return
|
|
57
91
|
with self._lock:
|
|
58
92
|
watchers = list(self._watchers)
|
|
59
93
|
|
package/tui/messages.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
|
|
2
|
+
from pathlib import Path
|
|
3
3
|
from typing import Literal
|
|
4
4
|
|
|
5
5
|
from textual.content import Content
|
|
@@ -72,9 +72,12 @@ class Flash(Message):
|
|
|
72
72
|
duration: float | None = None
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
@dataclass
|
|
75
76
|
class ProjectDirectoryUpdated(Message):
|
|
76
77
|
"""The project directory may may changed."""
|
|
77
78
|
|
|
79
|
+
project_dir: Path | None = None
|
|
80
|
+
|
|
78
81
|
|
|
79
82
|
@dataclass
|
|
80
83
|
class SessionNavigate(Message):
|
package/tui/paths.py
CHANGED
|
@@ -2,7 +2,7 @@ from contextlib import suppress
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Final
|
|
4
4
|
|
|
5
|
-
from xdg_base_dirs import xdg_config_home, xdg_data_home
|
|
5
|
+
from xdg_base_dirs import xdg_config_home, xdg_data_home
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
APP_NAME: Final[str] = "tui"
|
|
@@ -39,7 +39,7 @@ def get_config() -> Path:
|
|
|
39
39
|
|
|
40
40
|
def get_state() -> Path:
|
|
41
41
|
"""Return (possibly creating) the application state directory."""
|
|
42
|
-
path =
|
|
42
|
+
path = Path.home() / ".cdh" / "state" / APP_NAME
|
|
43
43
|
with suppress(OSError):
|
|
44
44
|
path.mkdir(0o700, exist_ok=True, parents=True)
|
|
45
45
|
return path
|
|
@@ -65,7 +65,7 @@ def get_log() -> Path:
|
|
|
65
65
|
Path to log directory.
|
|
66
66
|
|
|
67
67
|
"""
|
|
68
|
-
path = Path.home() / ".
|
|
68
|
+
path = Path.home() / ".cdh" / "logs"
|
|
69
69
|
with suppress(OSError):
|
|
70
70
|
path.mkdir(0o700, exist_ok=True, parents=True)
|
|
71
71
|
return path
|
|
Binary file
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from textual import on
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.binding import Binding
|
|
8
|
+
from textual.containers import Horizontal, Vertical, VerticalScroll
|
|
9
|
+
from textual.screen import ModalScreen
|
|
10
|
+
from textual.widgets import Button, Checkbox, Footer, Static
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ComponentPickerScreen(ModalScreen[Optional[list[str]]]):
|
|
14
|
+
"""Modal screen for selecting application components.
|
|
15
|
+
|
|
16
|
+
Returns the list of selected component ids, or None if cancelled.
|
|
17
|
+
If ``allow_empty`` is False (default), confirming with no selection
|
|
18
|
+
shows a notification and does not dismiss the screen.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
BINDINGS = [
|
|
22
|
+
Binding("escape", "cancel", "Cancel", show=False),
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
CSS = """
|
|
26
|
+
ComponentPickerScreen {
|
|
27
|
+
align: center middle;
|
|
28
|
+
background: rgba(0, 0, 0, 0.7);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#picker-dialog {
|
|
32
|
+
width: 78;
|
|
33
|
+
height: auto;
|
|
34
|
+
max-height: 90%;
|
|
35
|
+
background: #111;
|
|
36
|
+
border: solid #555;
|
|
37
|
+
padding: 0 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#picker-title {
|
|
41
|
+
height: 1;
|
|
42
|
+
background: #333;
|
|
43
|
+
color: #fff;
|
|
44
|
+
padding: 0 1;
|
|
45
|
+
text-style: bold;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#picker-subtitle {
|
|
49
|
+
height: 1;
|
|
50
|
+
color: #aaa;
|
|
51
|
+
padding: 0 1;
|
|
52
|
+
margin-bottom: 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#picker-list {
|
|
56
|
+
height: auto;
|
|
57
|
+
max-height: 22;
|
|
58
|
+
padding: 0 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.component-row {
|
|
62
|
+
height: 2;
|
|
63
|
+
padding: 0 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.component-row Checkbox {
|
|
67
|
+
width: 1fr;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#picker-buttons {
|
|
71
|
+
height: 3;
|
|
72
|
+
align: center middle;
|
|
73
|
+
background: #222;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#picker-buttons Button {
|
|
77
|
+
margin: 0 1;
|
|
78
|
+
background: #444;
|
|
79
|
+
color: #fff;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#picker-buttons Button:hover {
|
|
83
|
+
background: #666;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#picker-buttons Button:focus {
|
|
87
|
+
background: #555;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#picker-buttons #picker-confirm {
|
|
91
|
+
background: #1a4d8f;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#picker-buttons #picker-confirm:hover {
|
|
95
|
+
background: #2870c2;
|
|
96
|
+
}
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
title: str = "Select Application Components",
|
|
102
|
+
subtitle: str = "Choose the components for this project.",
|
|
103
|
+
allow_empty: bool = False,
|
|
104
|
+
) -> None:
|
|
105
|
+
super().__init__()
|
|
106
|
+
self._title_text = title
|
|
107
|
+
self._subtitle_text = subtitle
|
|
108
|
+
self._allow_empty = allow_empty
|
|
109
|
+
|
|
110
|
+
def compose(self) -> ComposeResult:
|
|
111
|
+
with Vertical(id="picker-dialog"):
|
|
112
|
+
yield Static(self._title_text, id="picker-title")
|
|
113
|
+
yield Static(self._subtitle_text, id="picker-subtitle")
|
|
114
|
+
with VerticalScroll(id="picker-list"):
|
|
115
|
+
from cdh.scaffold import COMPONENTS
|
|
116
|
+
|
|
117
|
+
for spec in COMPONENTS:
|
|
118
|
+
label = f"{spec.label} \u2014 {spec.description}"
|
|
119
|
+
yield Checkbox(label, id=f"comp-{spec.id}", classes="component-row")
|
|
120
|
+
with Horizontal(id="picker-buttons"):
|
|
121
|
+
yield Button("Cancel", id="picker-cancel")
|
|
122
|
+
yield Button("Confirm", id="picker-confirm", variant="primary")
|
|
123
|
+
yield Footer()
|
|
124
|
+
|
|
125
|
+
def action_cancel(self) -> None:
|
|
126
|
+
self.dismiss(None)
|
|
127
|
+
|
|
128
|
+
@on(Button.Pressed, "#picker-cancel")
|
|
129
|
+
def on_cancel(self) -> None:
|
|
130
|
+
self.dismiss(None)
|
|
131
|
+
|
|
132
|
+
@on(Button.Pressed, "#picker-confirm")
|
|
133
|
+
def on_confirm(self) -> None:
|
|
134
|
+
selected: list[str] = []
|
|
135
|
+
from cdh.scaffold import COMPONENTS
|
|
136
|
+
|
|
137
|
+
for spec in COMPONENTS:
|
|
138
|
+
cb = self.query_one(f"#comp-{spec.id}", Checkbox)
|
|
139
|
+
if cb.value:
|
|
140
|
+
selected.append(spec.id)
|
|
141
|
+
if not selected and not self._allow_empty:
|
|
142
|
+
self.notify(
|
|
143
|
+
"Select at least one application component",
|
|
144
|
+
title="No components selected",
|
|
145
|
+
severity="error",
|
|
146
|
+
)
|
|
147
|
+
return
|
|
148
|
+
self.dismiss(selected)
|