@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.
Files changed (147) hide show
  1. package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
  2. package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
  3. package/cdh/cli.py +473 -87
  4. package/cdh/scaffold.py +386 -121
  5. package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
  6. package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
  7. package/onecode/__pycache__/config.cpython-314.pyc +0 -0
  8. package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
  9. package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
  10. package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
  11. package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
  12. package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
  13. package/onecode/agent/agents/types.py +123 -79
  14. package/onecode/agent/cdh_loader.py +26 -13
  15. package/onecode/agent/engine.py +426 -235
  16. package/onecode/agent/onecode_agent_acp.py +202 -75
  17. package/onecode/agent/session.py +8 -13
  18. package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
  19. package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
  20. package/onecode/agent/tools/agent_tools.py +4 -4
  21. package/onecode/agent/tools/todo_tools.py +188 -0
  22. package/onecode/cli.py +3 -3
  23. package/onecode/commands.py +439 -194
  24. package/onecode/config.py +3 -3
  25. package/onecode/config_screen.py +4 -2
  26. package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
  27. package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
  28. package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
  29. package/onecode/models/errors.py +11 -1
  30. package/onecode/models/messages.py +35 -16
  31. package/onecode/models/provider.py +243 -12
  32. package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
  33. package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
  34. package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
  35. package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
  36. package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
  37. package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
  38. package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
  39. package/onecode/models/providers/anthropic_provider.py +4 -2
  40. package/onecode/models/providers/deepseek_provider.py +2 -0
  41. package/onecode/models/providers/glm_provider.py +2 -1
  42. package/onecode/models/providers/minimax_provider.py +2 -1
  43. package/onecode/models/providers/minimaxi_provider.py +76 -25
  44. package/onecode/models/providers/ollama_provider.py +2 -1
  45. package/onecode/models/providers/openai_provider.py +4 -0
  46. package/onecode/tasks/manager.py +1 -1
  47. package/package.json +1 -1
  48. package/pyproject.toml +10 -0
  49. package/run.js +24 -7
  50. package/tui/__pycache__/agents.cpython-314.pyc +0 -0
  51. package/tui/__pycache__/app.cpython-314.pyc +0 -0
  52. package/tui/__pycache__/db.cpython-314.pyc +0 -0
  53. package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
  54. package/tui/__pycache__/messages.cpython-314.pyc +0 -0
  55. package/tui/__pycache__/paths.cpython-314.pyc +0 -0
  56. package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
  57. package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
  58. package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
  59. package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
  60. package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
  61. package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
  62. package/tui/acp/agent.py +60 -11
  63. package/tui/acp/api.py +6 -0
  64. package/tui/acp/messages.py +11 -0
  65. package/tui/acp/prompt.py +1 -1
  66. package/tui/acp/protocol.py +31 -0
  67. package/tui/agents.py +8 -7
  68. package/tui/app.py +60 -8
  69. package/tui/data/agents/ampcode.com.toml +1 -0
  70. package/tui/data/agents/augmentcode.com.toml +1 -0
  71. package/tui/data/agents/cdh.cloud-dev-harness.toml +3 -2
  72. package/tui/data/agents/crowai.dev.toml +1 -0
  73. package/tui/data/agents/deepagents.langchain.com +1 -0
  74. package/tui/data/agents/docker.com.toml +1 -0
  75. package/tui/data/agents/goose.ai.toml +1 -0
  76. package/tui/data/agents/inference.huggingface.co.toml +1 -1
  77. package/tui/data/agents/kiro.dev.toml +1 -0
  78. package/tui/data/agents/onecode.dev.toml +22 -0
  79. package/tui/data/agents/openclaw.ai.toml +1 -1
  80. package/tui/data/agents/openhands.dev.toml +1 -0
  81. package/tui/data/agents/stakpak.dev.toml +1 -0
  82. package/tui/data/agents/vibe.mistral.ai.toml +1 -0
  83. package/tui/data/agents/vtcode.dev.toml +1 -0
  84. package/tui/db.py +23 -0
  85. package/tui/directory_watcher.py +35 -1
  86. package/tui/messages.py +4 -1
  87. package/tui/paths.py +3 -3
  88. package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
  89. package/tui/screens/component_picker.py +148 -0
  90. package/tui/screens/main.py +47 -95
  91. package/tui/screens/projects.tcss +10 -0
  92. package/tui/screens/projects_app.py +33 -5
  93. package/tui/screens/projects_screen.py +171 -9
  94. package/tui/screens/store.py +1 -1
  95. package/tui/settings_schema.py +1 -1
  96. package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
  97. package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
  98. package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
  99. package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
  100. package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
  101. package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
  102. package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
  103. package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
  104. package/tui/widgets/ask_user.py +1 -1
  105. package/tui/widgets/conversation.py +99 -42
  106. package/tui/widgets/modified_files.py +191 -65
  107. package/tui/widgets/plan.py +22 -0
  108. package/tui/widgets/project_directory_tree.py +22 -10
  109. package/tui/widgets/project_grid_select.py +23 -1
  110. package/tui/widgets/question.py +1 -1
  111. package/tui/widgets/subagent.py +202 -36
  112. package/tui/widgets/subagent_screen.py +186 -0
  113. package/tui/widgets/tool_call.py +5 -8
  114. package/cdh/__pycache__/__init__.cpython-313.pyc +0 -0
  115. package/cdh/__pycache__/scaffold.cpython-313.pyc +0 -0
  116. package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
  117. package/onecode/__pycache__/mock_data.cpython-314.pyc +0 -0
  118. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-313.pyc +0 -0
  119. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-314.pyc +0 -0
  120. package/onecode/agent/__pycache__/harness_skill.cpython-314.pyc +0 -0
  121. package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
  122. package/onecode/agent/tools/task_tools.py +0 -249
  123. package/onecode/codebase/__pycache__/config.cpython-314.pyc +0 -0
  124. package/onecode/models/providers/__pycache__/mock_provider.cpython-314.pyc +0 -0
  125. package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
  126. package/tui/__pycache__/danger.cpython-314.pyc +0 -0
  127. package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
  128. package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
  129. package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
  130. package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
  131. package/tui/screens/__pycache__/settings.cpython-314.pyc +0 -0
  132. package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
  133. package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
  134. package/tui/widgets/__pycache__/conversation.cpython-313.pyc +0 -0
  135. package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
  136. package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
  137. package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
  138. package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
  139. package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
  140. package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
  141. package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
  142. package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
  143. package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
  144. package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
  145. package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
  146. package/tui/widgets/__pycache__/terminal.cpython-313.pyc +0 -0
  147. 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
- self.launch_agent(
880
- agent_identity,
881
- agent_session_id=agent_session_id,
882
- )
883
- return
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(f"\n CDH powering down. Goodbye!\n Session ID: {sid or '-'}\n")
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,5 +1,6 @@
1
1
  # Schema defined in agent_schema.py
2
2
  # https://github.com/tao12345666333/amp-acp
3
+ active = false
3
4
 
4
5
  identity = "ampcode.com"
5
6
  name = "Amp (AmpCode)"
@@ -1,5 +1,6 @@
1
1
  # Schema defined in agent_schema.py
2
2
  # https://github.com/augmentcode/auggie
3
+ active = false
3
4
 
4
5
  identity = "augmentcode.com"
5
6
  name = "Auggie (Augment Code)"
@@ -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 = "CDH Agent"
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 = "Cloud Dev Harness - cloud-native development Agent framework with LLM provider integration"
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"
@@ -1,5 +1,6 @@
1
1
  # Schema defined in agent_schema.py
2
2
  # https://github.com/crow-cli/crow-cli
3
+ active = false
3
4
 
4
5
  identity = "crowai.dev"
5
6
  name = "crow-cli"
@@ -1,3 +1,4 @@
1
+ active = false
1
2
  identity = "deepagents.langchain.com"
2
3
  name = "Deep Agents"
3
4
  short_name = "deepagents"
@@ -1,5 +1,6 @@
1
1
  # Schema defined in agent_schema.py
2
2
  # https://github.com/docker/cagent
3
+ active = false
3
4
 
4
5
  identity = "docker.com"
5
6
  name = "Docker cagent"
@@ -1,5 +1,6 @@
1
1
  # Schema defined in agent_schema.py
2
2
  # https://github.com/block/goose
3
+ active = false
3
4
 
4
5
  identity = "goose.ai"
5
6
  name = "Goose"
@@ -1,6 +1,6 @@
1
1
  # Schema defined in agent_schema.py
2
2
 
3
- active = true
3
+ active = false
4
4
  identity = "inference.huggingface.co"
5
5
  name = "Hugging Face Inference Providers"
6
6
  short_name = "hf"
@@ -1,5 +1,6 @@
1
1
  # Schema defined in agent_schema.py
2
2
  # https://kiro.dev/cli/
3
+ active = false
3
4
 
4
5
  identity = "kiro.dev"
5
6
  name = "Kiro CLI"
@@ -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]
@@ -2,7 +2,7 @@
2
2
  # https://docs.openclaw.ai/cli/acp
3
3
 
4
4
  # Could not get Openclaw ACP to work
5
- active = true
5
+ active = false
6
6
 
7
7
  identity = "openclaw.ai"
8
8
  name = "OpenClaw"
@@ -1,5 +1,6 @@
1
1
  # Schema defined in agent_schema.py
2
2
  # https://www.claude.com/product/claude-code
3
+ active = false
3
4
 
4
5
  identity = "openhands.dev"
5
6
  name = "OpenHands"
@@ -1,5 +1,6 @@
1
1
  # Schema defined in agent_schema.py
2
2
  # https://github.com/stakpak/agent
3
+ active = false
3
4
 
4
5
  identity = "stakpak.dev"
5
6
  name = "Stakpak Agent"
@@ -1,5 +1,6 @@
1
1
  # Schema defined in agent_schema.py
2
2
  # https://www.claude.com/product/claude-code
3
+ active = false
3
4
 
4
5
  identity = "vibe.mistral.ai"
5
6
  name = "Mistral Vibe"
@@ -1,5 +1,6 @@
1
1
  # Schema defined in agent_schema.py
2
2
  # https://github.com/vinhnx/vtcode
3
+ active = false
3
4
 
4
5
  identity = "vtcode.dev"
5
6
  name = "VT Code"
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:
@@ -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, xdg_state_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 = xdg_state_home() / APP_NAME
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() / ".onecode" / "logs"
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
@@ -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)