@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
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from functools import partial
2
3
  from pathlib import Path
3
4
  import random
@@ -8,10 +9,10 @@ from textual import getters
8
9
  from textual.binding import Binding
9
10
  from textual.command import Hit, Hits, Provider, DiscoveryHit
10
11
  from textual.content import Content
11
- from textual.events import ScreenResume, Click
12
+ from textual.events import ScreenResume
12
13
  from textual.screen import Screen
13
14
  from textual.reactive import var, reactive
14
- from textual.widgets import Footer, OptionList, DirectoryTree, Tree, Static
15
+ from textual.widgets import Footer, OptionList, DirectoryTree, Tree
15
16
  from textual import containers
16
17
  from textual.widget import Widget
17
18
 
@@ -29,7 +30,6 @@ from tui.widgets.conversation import Conversation
29
30
  from tui.widgets.project_directory_tree import ProjectDirectoryTree
30
31
  from tui.widgets.side_bar import SideBar
31
32
  from tui.widgets.session_tabs import SessionsTabs, SessionLabel
32
- from onecode.agent.cdh_loader import CdhProjectLoader
33
33
 
34
34
 
35
35
  class ModeProvider(Provider):
@@ -102,7 +102,7 @@ class MainScreen(Screen, can_focus=False):
102
102
  column = reactive(False)
103
103
  column_width = reactive(80)
104
104
  scrollbar = reactive("")
105
- project_path: var[Path] = var(Path("./").expanduser().absolute())
105
+ project_path: var[Path | None] = var(Path("./").expanduser().absolute())
106
106
 
107
107
  app = getters.app(A2TUIApp)
108
108
 
@@ -116,6 +116,7 @@ class MainScreen(Screen, can_focus=False):
116
116
  initial_prompt: str | None = None,
117
117
  ) -> None:
118
118
  super().__init__()
119
+ self._project_update_lock = asyncio.Lock()
119
120
  self.set_reactive(MainScreen.project_path, project_path)
120
121
  self._agent = agent
121
122
  self._agent_session_id = agent_session_id
@@ -154,30 +155,17 @@ class MainScreen(Screen, can_focus=False):
154
155
  id="context-panel",
155
156
  ),
156
157
  ]
157
- cdh_dir = CdhProjectLoader.find_cdh_dir(self.project_path)
158
- if cdh_dir is not None:
159
- panels.append(
160
- SideBar.Panel(
161
- "Project",
162
- ProjectDirectoryTree(
163
- self.project_path,
164
- id="project_directory_tree",
165
- ),
166
- flex=True,
167
- id="project-panel",
168
- ),
169
- )
170
- else:
171
- panels.append(
172
- SideBar.Panel(
173
- "No Project",
174
- Static(
175
- "[bold]No .cdh project[/]\n\nClick to initialize .cdh in this directory",
176
- id="init-cdh-hint",
177
- ),
178
- id="no-project-panel",
179
- ),
180
- )
158
+ # panels.append(
159
+ # SideBar.Panel(
160
+ # "Project",
161
+ # ProjectDirectoryTree(
162
+ # self.project_path,
163
+ # id="project_directory_tree",
164
+ # ),
165
+ # flex=True,
166
+ # id="project-panel",
167
+ # ),
168
+ # )
181
169
  panels.append(
182
170
  SideBar.Panel(
183
171
  "Modified Files",
@@ -217,12 +205,22 @@ class MainScreen(Screen, can_focus=False):
217
205
  self.post_message(messages.SessionNavigate(self.screen.id, +1))
218
206
 
219
207
  @on(messages.ProjectDirectoryUpdated)
220
- async def on_project_directory_update(self) -> None:
221
- self.project_path = self.app.project_dir
222
- sidebar = self.query_one(SideBar)
223
- cdh_dir = CdhProjectLoader.find_cdh_dir(self.project_path)
224
- if cdh_dir is not None:
225
- sidebar.remove_panel("no-project-panel")
208
+ async def on_project_directory_update(self, event: messages.ProjectDirectoryUpdated) -> None:
209
+ async with self._project_update_lock:
210
+ new_dir = event.project_dir
211
+ sidebar = self.query_one(SideBar)
212
+ if new_dir is None:
213
+ sidebar.remove_panel("project-panel")
214
+ self.project_path = None
215
+ if mf := sidebar.query_one_optional("#modified_files", ModifiedFiles):
216
+ mf.refresh_files()
217
+ self._swap_directory_watcher(None)
218
+ return
219
+ if self.project_path == new_dir and sidebar.has_panel("project-panel"):
220
+ if mf := sidebar.query_one_optional("#modified_files", ModifiedFiles):
221
+ mf.refresh_files()
222
+ return
223
+ self.project_path = new_dir
226
224
  if not sidebar.has_panel("project-panel"):
227
225
  collapsible = await sidebar.add_panel(
228
226
  SideBar.Panel(
@@ -238,62 +236,23 @@ class MainScreen(Screen, can_focus=False):
238
236
  tree = collapsible.query_one(ProjectDirectoryTree)
239
237
  tree.data_bind(path=MainScreen.project_path)
240
238
  tree.guide_depth = 3
241
- else:
242
- tree = sidebar.query_one(ProjectDirectoryTree)
243
- tree.path = self.project_path
244
- await tree.reload()
245
- else:
246
- sidebar.remove_panel("project-panel")
247
- if not sidebar.has_panel("no-project-panel"):
248
- await sidebar.add_panel(
249
- SideBar.Panel(
250
- "No Project",
251
- Static(
252
- "[bold]No .cdh project[/]\n\nClick to initialize .cdh in this directory",
253
- id="init-cdh-hint",
254
- ),
255
- id="no-project-panel",
256
- ),
257
- )
258
- if mf := sidebar.query_one_optional("#modified_files", ModifiedFiles):
259
- mf.refresh_files()
260
-
261
- @on(Click, "#init-cdh-hint")
262
- def on_init_cdh_click(self) -> None:
263
- from pathlib import Path
264
- from onecode.config_screen import EditFieldScreen
265
- from onecode.agent.cdh_loader import CdhProjectLoader
266
-
267
- default_path = str(self.project_path.resolve())
268
- self.app.push_screen(
269
- EditFieldScreen("Directory to initialize .cdh", default_path),
270
- self._on_init_cdh_path,
271
- )
239
+
240
+ if mf := sidebar.query_one_optional("#modified_files", ModifiedFiles):
241
+ mf.refresh_files()
242
+ self._swap_directory_watcher(new_dir)
272
243
 
273
- def _on_init_cdh_path(self, path_str: str | None) -> None:
274
- from pathlib import Path
275
- from onecode.agent.cdh_loader import CdhProjectLoader
244
+ def _swap_directory_watcher(self, project_path: Path | None) -> None:
245
+ try:
246
+ self.conversation.directory_watcher_swap(project_path)
247
+ except Exception:
248
+ pass
276
249
 
277
- if not path_str:
278
- return
250
+ def _has_project_panel(self) -> bool:
279
251
  try:
280
- target = Path(path_str).expanduser().resolve()
281
- if not target.is_dir():
282
- self.notify(f"Not a directory: {target}", severity="error")
283
- return
252
+ sidebar = self.query_one(SideBar)
284
253
  except Exception:
285
- self.notify("Invalid path", severity="error")
286
- return
287
- existing = CdhProjectLoader.find_cdh_dir(target)
288
- if existing is not None:
289
- self.notify(f".cdh already exists at {existing}", severity="warning")
290
- return
291
- name = target.name
292
- from cdh.scaffold import scaffold_dlc_project
293
- scaffold_dlc_project(target, name)
294
- CdhProjectLoader.init_project(target, name)
295
- self.notify(f"Initialized .cdh in {target}")
296
- self.post_message(messages.ProjectDirectoryUpdated())
254
+ return False
255
+ return sidebar.has_panel("project-panel")
297
256
 
298
257
  @on(DirectoryTree.FileSelected, "ProjectDirectoryTree")
299
258
  def on_project_directory_tree_selected(self, event: Tree.NodeSelected):
@@ -303,15 +262,8 @@ class MainScreen(Screen, can_focus=False):
303
262
  @on(acp_messages.Plan)
304
263
  async def on_acp_plan(self, message: acp_messages.Plan):
305
264
  message.stop()
306
- entries = [
307
- Plan.Entry(
308
- Content(entry["content"]),
309
- entry.get("priority", "medium"),
310
- entry.get("status", "pending"),
311
- )
312
- for entry in message.entries
313
- ]
314
- self.query_one("SideBar Plan", Plan).entries = entries
265
+ from tui.widgets.plan import entries_from_dicts
266
+ self.query_one("SideBar Plan", Plan).entries = entries_from_dicts(message.entries)
315
267
 
316
268
  @on(messages.SessionUpdate)
317
269
  async def on_session_update(self, event: messages.SessionUpdate) -> None:
@@ -20,6 +20,16 @@ ProjectsScreen {
20
20
  text-style: italic;
21
21
  }
22
22
 
23
+ #actions {
24
+ height: auto;
25
+ align-horizontal: center;
26
+ margin: 0 1 1 1;
27
+ }
28
+
29
+ #actions Button {
30
+ margin: 0 1;
31
+ }
32
+
23
33
 
24
34
  ProjectGridSelect {
25
35
  &:empty {
@@ -226,13 +226,24 @@ class ProjectsApp(App):
226
226
  except Exception:
227
227
  ws = Path.cwd().resolve()
228
228
  name = ws.name
229
- from cdh.scaffold import scaffold_dlc_project
230
- scaffold_dlc_project(ws, name)
231
- CdhProjectLoader.init_project(ws, name)
232
229
  projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
233
230
  projects_dir.mkdir(parents=True, exist_ok=True)
231
+ proj_file = projects_dir / f"{name}.yaml"
232
+ if proj_file.exists():
233
+ self.notify(
234
+ f"Project '{name}' already exists in the project list",
235
+ severity="error",
236
+ )
237
+ return
238
+ from cdh.scaffold import scaffold_dlc_project
239
+ try:
240
+ scaffold_dlc_project(ws, name)
241
+ except (ValueError, RuntimeError) as e:
242
+ self.notify(str(e), severity="error")
243
+ return
244
+ CdhProjectLoader.init_project(ws, name)
234
245
  proj_data = {"name": name, "path": str(ws), "description": ""}
235
- (projects_dir / f"{name}.yaml").write_text(yaml.dump(proj_data))
246
+ proj_file.write_text(yaml.dump(proj_data))
236
247
  cfg = load_config()
237
248
  cfg.current_project = name
238
249
  cfg.current_project_path = str(ws)
@@ -262,9 +273,26 @@ class ProjectsApp(App):
262
273
  self.notify(f".cdh already exists at {existing}", severity="warning")
263
274
  return
264
275
  name = target.name
276
+ projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
277
+ projects_dir.mkdir(parents=True, exist_ok=True)
278
+ proj_file = projects_dir / f"{name}.yaml"
279
+ if proj_file.exists():
280
+ self.notify(
281
+ f"Project '{name}' already exists in the project list",
282
+ severity="error",
283
+ )
284
+ return
265
285
  from cdh.scaffold import init_dlc_project
266
- init_dlc_project(target, name)
286
+ try:
287
+ init_dlc_project(target, name)
288
+ except (ValueError, RuntimeError) as e:
289
+ self.notify(str(e), severity="error")
290
+ return
267
291
  CdhProjectLoader.init_project(target, name)
292
+ import yaml
293
+ proj_data = {"name": name, "path": str(target), "description": ""}
294
+ proj_file.write_text(yaml.dump(proj_data))
295
+ self._refresh()
268
296
  self.notify(f"Initialized .cdh in {target}")
269
297
 
270
298
  def _load_project(self) -> None:
@@ -2,7 +2,6 @@ from time import monotonic
2
2
 
3
3
  from textual.app import ComposeResult
4
4
  from textual.binding import Binding
5
- from textual.events import ScreenResume
6
5
  from textual.screen import ModalScreen
7
6
  from textual import getters
8
7
  from textual.widget import Widget
@@ -17,12 +16,26 @@ from tui.app import A2TUIApp
17
16
  from tui.widgets.grid_select import GridSelect
18
17
  from tui.widgets.project_grid_select import ProjectGridSelect
19
18
  from tui.widgets.project_summary import ProjectSummary
19
+ from tui.screens.component_picker import ComponentPickerScreen
20
20
 
21
21
 
22
22
  CONFIRM_TIMEOUT = 5.0
23
23
  INSTRUCTIONS_NO_PROJECTS = "Your projects will be shown here."
24
24
 
25
25
 
26
+ def _project_db_path(name: str):
27
+ """Return the path of the project entry in ``~/.cdh/projects/``
28
+ for *name* across all supported extensions, or ``None`` if the
29
+ project is not registered yet.
30
+ """
31
+ projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
32
+ for ext in ("yaml", "yml", "json"):
33
+ pf = projects_dir / f"{name}.{ext}"
34
+ if pf.exists():
35
+ return pf
36
+ return None
37
+
38
+
26
39
  class ProjectsScreen(ModalScreen[str]):
27
40
  CSS_PATH = "projects.tcss"
28
41
  BINDINGS = [
@@ -41,7 +54,7 @@ class ProjectsScreen(ModalScreen[str]):
41
54
  yield widgets.Label("Projects")
42
55
  yield widgets.Static(INSTRUCTIONS_NO_PROJECTS, classes="instructions")
43
56
  yield ProjectGridSelect()
44
- with containers.Center():
57
+ with containers.Horizontal(id="actions"):
45
58
  yield widgets.Button("+ New Project", id="new-project", variant="primary")
46
59
  yield widgets.Button("Init .cdh (i)", id="init-project")
47
60
  yield widgets.Footer()
@@ -89,12 +102,95 @@ class ProjectsScreen(ModalScreen[str]):
89
102
  self.notify(f"Deleted project: {project_name}")
90
103
 
91
104
  def action_new_project(self) -> None:
92
- self.dismiss("__new__")
105
+ from pathlib import Path
106
+ from onecode.config_screen import EditFieldScreen
107
+
108
+ default_path = str(Path.cwd().resolve())
109
+ self.app.push_screen(
110
+ EditFieldScreen("Project path", default_path),
111
+ self._on_new_project_path,
112
+ )
113
+
114
+ def _on_new_project_path(self, path_str: str | None) -> None:
115
+ from pathlib import Path
116
+
117
+ if not path_str:
118
+ return
119
+ try:
120
+ target = Path(path_str).expanduser().resolve()
121
+ except Exception:
122
+ self.notify("Invalid path", severity="error")
123
+ return
124
+ if _project_db_path(target.name) is not None:
125
+ self.notify(
126
+ f"Project '{target.name}' is already in the project list",
127
+ severity="warning",
128
+ )
129
+ return
130
+ self.app.push_screen(
131
+ ComponentPickerScreen(
132
+ title="New Project — Select Components",
133
+ subtitle=(
134
+ f"Project path: {target}\n"
135
+ "Cross-cutting items (contracts, shared types, etc.) are created automatically."
136
+ ),
137
+ allow_empty=False,
138
+ ),
139
+ lambda picked: self._on_new_project_components(target, picked),
140
+ )
141
+
142
+ def _on_new_project_components(
143
+ self,
144
+ target: "Path",
145
+ picked: list[str] | None,
146
+ ) -> None:
147
+ if picked is None:
148
+ self.notify("New project cancelled", severity="warning")
149
+ return
150
+ if not picked:
151
+ return
152
+ self._do_new_project(target, picked)
153
+
154
+ def _do_new_project(
155
+ self,
156
+ target: "Path",
157
+ components: list[str],
158
+ ) -> None:
159
+ from pathlib import Path
160
+ from onecode.agent.cdh_loader import CdhProjectLoader
161
+
162
+ name = target.name
163
+ from cdh.scaffold import scaffold_dlc_project
164
+
165
+ try:
166
+ scaffold_dlc_project(target, name, components=components)
167
+ except (ValueError, RuntimeError) as e:
168
+ self.notify(str(e), severity="error")
169
+ return
170
+ if _project_db_path(name) is not None:
171
+ self.notify(
172
+ f"Project '{name}' is already in the project list",
173
+ severity="error",
174
+ )
175
+ return
176
+ projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
177
+ projects_dir.mkdir(parents=True, exist_ok=True)
178
+ proj_file = projects_dir / f"{name}.yaml"
179
+ CdhProjectLoader.init_project(target, name)
180
+ import yaml
181
+ proj_data = {"name": name, "path": str(target), "description": ""}
182
+ proj_file.write_text(yaml.dump(proj_data))
183
+ widget = ProjectSummary(name, str(target), id=name)
184
+ self.project_grid_select.mount(widget)
185
+ self.project_grid_select.highlighted = len(self.project_grid_select.children) - 1
186
+ self.notify(
187
+ f"Created project '{name}' at {target} "
188
+ f"(components: {', '.join(components)})"
189
+ )
93
190
 
94
191
  def action_init_project(self) -> None:
95
192
  from pathlib import Path
96
193
  from onecode.config_screen import EditFieldScreen
97
- from onecode.agent.cdh_loader import CdhProjectLoader
98
194
 
99
195
  default_path = str(Path.cwd().resolve())
100
196
  self.app.push_screen(
@@ -117,14 +213,80 @@ class ProjectsScreen(ModalScreen[str]):
117
213
  self.notify("Invalid path", severity="error")
118
214
  return
119
215
  existing = CdhProjectLoader.find_cdh_dir(target)
120
- if existing is not None:
216
+ if existing is not None and existing.parent == target:
121
217
  self.notify(f".cdh already exists at {existing}", severity="warning")
122
218
  return
219
+ if _project_db_path(target.name) is not None:
220
+ self.notify(
221
+ f"Project '{target.name}' is already in the project list",
222
+ severity="warning",
223
+ )
224
+ return
225
+ self.app.push_screen(
226
+ ComponentPickerScreen(
227
+ title="Init .cdh — Select Components",
228
+ subtitle=(
229
+ f"Directory: {target}\n"
230
+ "Pick the components already present in this directory. "
231
+ "Empty selection is allowed (no apps/ created)."
232
+ ),
233
+ allow_empty=True,
234
+ ),
235
+ lambda picked: self._on_init_components(target, picked),
236
+ )
237
+
238
+ def _on_init_components(
239
+ self,
240
+ target: "Path",
241
+ picked: list[str] | None,
242
+ ) -> None:
243
+ if picked is None:
244
+ self.notify("Init cancelled", severity="warning")
245
+ return
246
+ self._do_init_project(target, picked)
247
+
248
+ def _do_init_project(
249
+ self,
250
+ target: "Path",
251
+ components: list[str],
252
+ ) -> None:
253
+ from pathlib import Path
254
+ from onecode.agent.cdh_loader import CdhProjectLoader
255
+ from cdh.scaffold import add_component, init_dlc_project
256
+
123
257
  name = target.name
124
- from cdh.scaffold import scaffold_dlc_project
125
- scaffold_dlc_project(target, name)
258
+ if _project_db_path(name) is not None:
259
+ self.notify(
260
+ f"Project '{name}' is already in the project list",
261
+ severity="error",
262
+ )
263
+ return
264
+ try:
265
+ init_dlc_project(target, name)
266
+ except (ValueError, RuntimeError) as e:
267
+ self.notify(str(e), severity="error")
268
+ return
269
+ for cid in components:
270
+ try:
271
+ add_component(target, cid)
272
+ except (ValueError, FileNotFoundError) as e:
273
+ self.notify(str(e), severity="error")
126
274
  CdhProjectLoader.init_project(target, name)
127
- self.notify(f"Initialized .cdh in {target}")
275
+ import yaml
276
+ projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
277
+ projects_dir.mkdir(parents=True, exist_ok=True)
278
+ proj_file = projects_dir / f"{name}.yaml"
279
+ proj_data = {"name": name, "path": str(target), "description": ""}
280
+ proj_file.write_text(yaml.dump(proj_data))
281
+ widget = ProjectSummary(name, str(target), id=name)
282
+ self.project_grid_select.mount(widget)
283
+ self.project_grid_select.highlighted = len(self.project_grid_select.children) - 1
284
+ suffix = (
285
+ f" (components: {', '.join(components)})"
286
+ if components
287
+ else " (no components)"
288
+ )
289
+ self.notify(f"Initialized .cdh in {target}{suffix}")
128
290
 
129
291
  @on(GridSelect.Selected)
130
292
  def on_selected(self, event: GridSelect.Selected) -> None:
@@ -135,7 +297,7 @@ class ProjectsScreen(ModalScreen[str]):
135
297
 
136
298
  @on(widgets.Button.Pressed, "#new-project")
137
299
  def on_new_project(self) -> None:
138
- self.dismiss("__new__")
300
+ self.action_new_project()
139
301
 
140
302
  @on(widgets.Button.Pressed, "#init-project")
141
303
  def on_init_project(self) -> None:
@@ -421,7 +421,7 @@ class StoreScreen(Screen):
421
421
  async def on_mount(self) -> None:
422
422
  self.app.settings_changed_signal.subscribe(self, self.setting_updated)
423
423
  if not self.app.settings.get("launcher.agents", str).strip():
424
- self.app.settings.set("launcher.agents", "cdh.cloud-dev-harness")
424
+ self.app.settings.set("launcher.agents", "onecode.dev")
425
425
  await self.app.save_settings()
426
426
  try:
427
427
  self._agents = await read_agents()
@@ -363,7 +363,7 @@ SCHEMA: list[SchemaDict] = [
363
363
  "key": "agents",
364
364
  "title": "Agents to show in the launcher",
365
365
  "type": "text",
366
- "default": "cdh.cloud-dev-harness",
366
+ "default": "onecode.dev",
367
367
  }
368
368
  ],
369
369
  },
@@ -260,5 +260,5 @@ class AskUserWidget(VerticalGroup):
260
260
  def _finish(self, value: str) -> None:
261
261
  self._answer = value
262
262
  self._done = True
263
- self.refresh(recompose=True, layout=True)
263
+ self.refresh(recompose=True)
264
264
  self.post_message(AskUserSubmitted(value, self._tool_id))