kollabor 0.4.9__py3-none-any.whl → 0.4.15__py3-none-any.whl

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 (192) hide show
  1. agents/__init__.py +2 -0
  2. agents/coder/__init__.py +0 -0
  3. agents/coder/agent.json +4 -0
  4. agents/coder/api-integration.md +2150 -0
  5. agents/coder/cli-pretty.md +765 -0
  6. agents/coder/code-review.md +1092 -0
  7. agents/coder/database-design.md +1525 -0
  8. agents/coder/debugging.md +1102 -0
  9. agents/coder/dependency-management.md +1397 -0
  10. agents/coder/git-workflow.md +1099 -0
  11. agents/coder/refactoring.md +1454 -0
  12. agents/coder/security-hardening.md +1732 -0
  13. agents/coder/system_prompt.md +1448 -0
  14. agents/coder/tdd.md +1367 -0
  15. agents/creative-writer/__init__.py +0 -0
  16. agents/creative-writer/agent.json +4 -0
  17. agents/creative-writer/character-development.md +1852 -0
  18. agents/creative-writer/dialogue-craft.md +1122 -0
  19. agents/creative-writer/plot-structure.md +1073 -0
  20. agents/creative-writer/revision-editing.md +1484 -0
  21. agents/creative-writer/system_prompt.md +690 -0
  22. agents/creative-writer/worldbuilding.md +2049 -0
  23. agents/data-analyst/__init__.py +30 -0
  24. agents/data-analyst/agent.json +4 -0
  25. agents/data-analyst/data-visualization.md +992 -0
  26. agents/data-analyst/exploratory-data-analysis.md +1110 -0
  27. agents/data-analyst/pandas-data-manipulation.md +1081 -0
  28. agents/data-analyst/sql-query-optimization.md +881 -0
  29. agents/data-analyst/statistical-analysis.md +1118 -0
  30. agents/data-analyst/system_prompt.md +928 -0
  31. agents/default/__init__.py +0 -0
  32. agents/default/agent.json +4 -0
  33. agents/default/dead-code.md +794 -0
  34. agents/default/explore-agent-system.md +585 -0
  35. agents/default/system_prompt.md +1448 -0
  36. agents/kollabor/__init__.py +0 -0
  37. agents/kollabor/analyze-plugin-lifecycle.md +175 -0
  38. agents/kollabor/analyze-terminal-rendering.md +388 -0
  39. agents/kollabor/code-review.md +1092 -0
  40. agents/kollabor/debug-mcp-integration.md +521 -0
  41. agents/kollabor/debug-plugin-hooks.md +547 -0
  42. agents/kollabor/debugging.md +1102 -0
  43. agents/kollabor/dependency-management.md +1397 -0
  44. agents/kollabor/git-workflow.md +1099 -0
  45. agents/kollabor/inspect-llm-conversation.md +148 -0
  46. agents/kollabor/monitor-event-bus.md +558 -0
  47. agents/kollabor/profile-performance.md +576 -0
  48. agents/kollabor/refactoring.md +1454 -0
  49. agents/kollabor/system_prompt copy.md +1448 -0
  50. agents/kollabor/system_prompt.md +757 -0
  51. agents/kollabor/trace-command-execution.md +178 -0
  52. agents/kollabor/validate-config.md +879 -0
  53. agents/research/__init__.py +0 -0
  54. agents/research/agent.json +4 -0
  55. agents/research/architecture-mapping.md +1099 -0
  56. agents/research/codebase-analysis.md +1077 -0
  57. agents/research/dependency-audit.md +1027 -0
  58. agents/research/performance-profiling.md +1047 -0
  59. agents/research/security-review.md +1359 -0
  60. agents/research/system_prompt.md +492 -0
  61. agents/technical-writer/__init__.py +0 -0
  62. agents/technical-writer/agent.json +4 -0
  63. agents/technical-writer/api-documentation.md +2328 -0
  64. agents/technical-writer/changelog-management.md +1181 -0
  65. agents/technical-writer/readme-writing.md +1360 -0
  66. agents/technical-writer/style-guide.md +1410 -0
  67. agents/technical-writer/system_prompt.md +653 -0
  68. agents/technical-writer/tutorial-creation.md +1448 -0
  69. core/__init__.py +0 -2
  70. core/application.py +343 -88
  71. core/cli.py +229 -10
  72. core/commands/menu_renderer.py +463 -59
  73. core/commands/registry.py +14 -9
  74. core/commands/system_commands.py +2461 -14
  75. core/config/loader.py +151 -37
  76. core/config/service.py +18 -6
  77. core/events/bus.py +29 -9
  78. core/events/executor.py +205 -75
  79. core/events/models.py +27 -8
  80. core/fullscreen/command_integration.py +20 -24
  81. core/fullscreen/components/__init__.py +10 -1
  82. core/fullscreen/components/matrix_components.py +1 -2
  83. core/fullscreen/components/space_shooter_components.py +654 -0
  84. core/fullscreen/plugin.py +5 -0
  85. core/fullscreen/renderer.py +52 -13
  86. core/fullscreen/session.py +52 -15
  87. core/io/__init__.py +29 -5
  88. core/io/buffer_manager.py +6 -1
  89. core/io/config_status_view.py +7 -29
  90. core/io/core_status_views.py +267 -347
  91. core/io/input/__init__.py +25 -0
  92. core/io/input/command_mode_handler.py +711 -0
  93. core/io/input/display_controller.py +128 -0
  94. core/io/input/hook_registrar.py +286 -0
  95. core/io/input/input_loop_manager.py +421 -0
  96. core/io/input/key_press_handler.py +502 -0
  97. core/io/input/modal_controller.py +1011 -0
  98. core/io/input/paste_processor.py +339 -0
  99. core/io/input/status_modal_renderer.py +184 -0
  100. core/io/input_errors.py +5 -1
  101. core/io/input_handler.py +211 -2452
  102. core/io/key_parser.py +7 -0
  103. core/io/layout.py +15 -3
  104. core/io/message_coordinator.py +111 -2
  105. core/io/message_renderer.py +129 -4
  106. core/io/status_renderer.py +147 -607
  107. core/io/terminal_renderer.py +97 -51
  108. core/io/terminal_state.py +21 -4
  109. core/io/visual_effects.py +816 -165
  110. core/llm/agent_manager.py +1063 -0
  111. core/llm/api_adapters/__init__.py +44 -0
  112. core/llm/api_adapters/anthropic_adapter.py +432 -0
  113. core/llm/api_adapters/base.py +241 -0
  114. core/llm/api_adapters/openai_adapter.py +326 -0
  115. core/llm/api_communication_service.py +167 -113
  116. core/llm/conversation_logger.py +322 -16
  117. core/llm/conversation_manager.py +556 -30
  118. core/llm/file_operations_executor.py +84 -32
  119. core/llm/llm_service.py +934 -103
  120. core/llm/mcp_integration.py +541 -57
  121. core/llm/message_display_service.py +135 -18
  122. core/llm/plugin_sdk.py +1 -2
  123. core/llm/profile_manager.py +1183 -0
  124. core/llm/response_parser.py +274 -56
  125. core/llm/response_processor.py +16 -3
  126. core/llm/tool_executor.py +6 -1
  127. core/logging/__init__.py +2 -0
  128. core/logging/setup.py +34 -6
  129. core/models/resume.py +54 -0
  130. core/plugins/__init__.py +4 -2
  131. core/plugins/base.py +127 -0
  132. core/plugins/collector.py +23 -161
  133. core/plugins/discovery.py +37 -3
  134. core/plugins/factory.py +6 -12
  135. core/plugins/registry.py +5 -17
  136. core/ui/config_widgets.py +128 -28
  137. core/ui/live_modal_renderer.py +2 -1
  138. core/ui/modal_actions.py +5 -0
  139. core/ui/modal_overlay_renderer.py +0 -60
  140. core/ui/modal_renderer.py +268 -7
  141. core/ui/modal_state_manager.py +29 -4
  142. core/ui/widgets/base_widget.py +7 -0
  143. core/updates/__init__.py +10 -0
  144. core/updates/version_check_service.py +348 -0
  145. core/updates/version_comparator.py +103 -0
  146. core/utils/config_utils.py +685 -526
  147. core/utils/plugin_utils.py +1 -1
  148. core/utils/session_naming.py +111 -0
  149. fonts/LICENSE +21 -0
  150. fonts/README.md +46 -0
  151. fonts/SymbolsNerdFont-Regular.ttf +0 -0
  152. fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
  153. fonts/__init__.py +44 -0
  154. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
  155. kollabor-0.4.15.dist-info/RECORD +228 -0
  156. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
  157. plugins/agent_orchestrator/__init__.py +39 -0
  158. plugins/agent_orchestrator/activity_monitor.py +181 -0
  159. plugins/agent_orchestrator/file_attacher.py +77 -0
  160. plugins/agent_orchestrator/message_injector.py +135 -0
  161. plugins/agent_orchestrator/models.py +48 -0
  162. plugins/agent_orchestrator/orchestrator.py +403 -0
  163. plugins/agent_orchestrator/plugin.py +976 -0
  164. plugins/agent_orchestrator/xml_parser.py +191 -0
  165. plugins/agent_orchestrator_plugin.py +9 -0
  166. plugins/enhanced_input/box_styles.py +1 -0
  167. plugins/enhanced_input/color_engine.py +19 -4
  168. plugins/enhanced_input/config.py +2 -2
  169. plugins/enhanced_input_plugin.py +61 -11
  170. plugins/fullscreen/__init__.py +6 -2
  171. plugins/fullscreen/example_plugin.py +1035 -222
  172. plugins/fullscreen/setup_wizard_plugin.py +592 -0
  173. plugins/fullscreen/space_shooter_plugin.py +131 -0
  174. plugins/hook_monitoring_plugin.py +436 -78
  175. plugins/query_enhancer_plugin.py +66 -30
  176. plugins/resume_conversation_plugin.py +1494 -0
  177. plugins/save_conversation_plugin.py +98 -32
  178. plugins/system_commands_plugin.py +70 -56
  179. plugins/tmux_plugin.py +154 -78
  180. plugins/workflow_enforcement_plugin.py +94 -92
  181. system_prompt/default.md +952 -886
  182. core/io/input_mode_manager.py +0 -402
  183. core/io/modal_interaction_handler.py +0 -315
  184. core/io/raw_input_processor.py +0 -946
  185. core/storage/__init__.py +0 -5
  186. core/storage/state_manager.py +0 -84
  187. core/ui/widget_integration.py +0 -222
  188. core/utils/key_reader.py +0 -171
  189. kollabor-0.4.9.dist-info/RECORD +0 -128
  190. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
  191. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
  192. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
@@ -5,6 +5,7 @@ import sys
5
5
  from pathlib import Path
6
6
  import logging
7
7
  import shutil
8
+ from typing import Optional
8
9
 
9
10
  logger = logging.getLogger(__name__)
10
11
 
@@ -12,33 +13,235 @@ logger = logging.getLogger(__name__)
12
13
  IS_WINDOWS = sys.platform == "win32"
13
14
 
14
15
 
15
- def get_config_directory() -> Path:
16
- """Get the Kollabor configuration directory.
16
+ # ============================================================================
17
+ # Project Data Path Utilities
18
+ # ============================================================================
17
19
 
18
- Resolution order:
19
- 1. Local .kollabor-cli/ in current directory (project-specific override)
20
- 2. Global ~/.kollabor-cli/ (default for most users)
20
+ def encode_project_path(project_path: Path) -> str:
21
+ """Encode a project path to a safe directory name.
22
+
23
+ Replaces path separators (/ and \\) with underscores and strips
24
+ leading underscores for use as a directory name.
25
+
26
+ Args:
27
+ project_path: Path to encode
21
28
 
22
29
  Returns:
23
- Path to the configuration directory
30
+ Encoded string safe for use as directory name
31
+
32
+ Examples:
33
+ >>> encode_project_path(Path("/Users/malmazan/dev/hello_world"))
34
+ 'Users_malmazan_dev_hello_world'
35
+ >>> encode_project_path(Path("C:\\\\Users\\\\dev\\\\project"))
36
+ 'C_Users_dev_project'
24
37
  """
25
- local_config_dir = Path.cwd() / ".kollabor-cli"
26
- global_config_dir = Path.home() / ".kollabor-cli"
38
+ path_str = str(project_path.resolve())
39
+ # Replace path separators with underscores
40
+ encoded = path_str.replace("/", "_").replace("\\", "_")
41
+ # Remove leading underscore if present (from root /)
42
+ while encoded.startswith("_"):
43
+ encoded = encoded[1:]
44
+ return encoded
45
+
46
+
47
+ def decode_project_path(encoded: str) -> Path:
48
+ """Decode an encoded project path back to a Path.
49
+
50
+ Reverses the encoding done by encode_project_path().
51
+
52
+ Args:
53
+ encoded: Encoded project path string
54
+
55
+ Returns:
56
+ Original Path object
27
57
 
28
- if local_config_dir.exists():
29
- return local_config_dir
58
+ Examples:
59
+ >>> decode_project_path("Users_malmazan_dev_hello_world")
60
+ Path('/Users/malmazan/dev/hello_world')
61
+ >>> decode_project_path("C_Users_dev_project")
62
+ Path('C:\\\\Users\\\\dev\\\\project')
63
+ """
64
+ # Detect Windows paths (start with drive letter + underscore)
65
+ if len(encoded) > 1 and encoded[1] == "_" and encoded[0].isalpha():
66
+ # Windows: C_Users_... -> C:\Users\...
67
+ path_str = encoded[0] + ":\\" + encoded[2:].replace("_", "\\")
30
68
  else:
31
- return global_config_dir
69
+ # Unix: Users_malmazan_... -> /Users/malmazan/...
70
+ path_str = "/" + encoded.replace("_", "/")
71
+ return Path(path_str)
72
+
73
+
74
+ def get_project_data_dir(project_path: Path = None) -> Path:
75
+ """Get the centralized project data directory.
76
+
77
+ Returns ~/.kollabor-cli/projects/<encoded-path>/ for the current
78
+ or specified project.
79
+
80
+ Args:
81
+ project_path: Path to project directory. If None, uses Path.cwd()
82
+
83
+ Returns:
84
+ Path to project-specific data directory
85
+ """
86
+ if project_path is None:
87
+ project_path = Path.cwd()
88
+
89
+ encoded = encode_project_path(project_path)
90
+ return Path.home() / ".kollabor-cli" / "projects" / encoded
91
+
92
+
93
+ def get_conversations_dir(project_path: Path = None) -> Path:
94
+ """Get the conversations directory for a project.
95
+
96
+ Args:
97
+ project_path: Path to project directory. If None, uses Path.cwd()
98
+
99
+ Returns:
100
+ Path to project's conversations directory
101
+ """
102
+ return get_project_data_dir(project_path) / "conversations"
103
+
104
+
105
+ def get_logs_dir(project_path: Path = None) -> Path:
106
+ """Get the logs directory for a project.
107
+
108
+ Args:
109
+ project_path: Path to project directory. If None, uses Path.cwd()
110
+
111
+ Returns:
112
+ Path to project's logs directory
113
+ """
114
+ return get_project_data_dir(project_path) / "logs"
115
+
116
+
117
+ def get_local_agents_dir() -> Path | None:
118
+ """Get the local agents directory if it exists.
119
+
120
+ Checks for .kollabor-cli/agents/ in the current working directory.
121
+
122
+ Returns:
123
+ Path to local agents directory if it exists, None otherwise
124
+ """
125
+ local_agents_dir = Path.cwd() / ".kollabor-cli" / "agents"
126
+ if local_agents_dir.exists():
127
+ return local_agents_dir
128
+ return None
129
+
130
+
131
+ def get_local_agents_path() -> Path:
132
+ """Get the local agents directory path (for creation purposes).
133
+
134
+ Unlike get_local_agents_dir(), this returns the path even if it doesn't exist.
135
+ Use this when you need to create the local agents directory.
136
+
137
+ Returns:
138
+ Path to .kollabor-cli/agents/ in the current working directory
139
+ """
140
+ return Path.cwd() / ".kollabor-cli" / "agents"
141
+
142
+
143
+ def get_global_agents_dir() -> Path:
144
+ """Get the global agents directory.
145
+
146
+ Returns:
147
+ Path to ~/.kollabor-cli/agents/
148
+ """
149
+ return Path.home() / ".kollabor-cli" / "agents"
150
+
151
+ # CLI override for system prompt file (set via --system-prompt argument)
152
+ _cli_system_prompt_file: str | None = None
153
+
154
+
155
+ def set_cli_system_prompt_file(file_path: str | None) -> None:
156
+ """Set the CLI override for system prompt file.
157
+
158
+ Args:
159
+ file_path: Path to the system prompt file, or None to clear
160
+ """
161
+ global _cli_system_prompt_file
162
+ _cli_system_prompt_file = file_path
163
+ if file_path:
164
+ logger.info(f"CLI system prompt override set: {file_path}")
165
+
166
+
167
+ def _resolve_system_prompt_path(filename: str) -> Path | None:
168
+ """Resolve a system prompt filename to a full path.
169
+
170
+ Searches in order:
171
+ 1. As-is (if absolute path or exists in cwd)
172
+ 2. Local .kollabor-cli/agents/default/
173
+ 3. Global ~/.kollabor-cli/agents/default/
174
+
175
+ Args:
176
+ filename: The filename or path provided by the user
177
+
178
+ Returns:
179
+ Resolved Path if found, None otherwise
180
+ """
181
+ # Expand ~ in path
182
+ expanded = Path(filename).expanduser()
183
+
184
+ # 1. Check as-is (absolute path or relative from cwd)
185
+ if expanded.exists():
186
+ return expanded
187
+
188
+ # If it's an absolute path that doesn't exist, don't search further
189
+ if expanded.is_absolute():
190
+ return None
191
+
192
+ # Get just the filename for searching in directories
193
+ name = expanded.name
194
+
195
+ # Also try with .md extension if not present
196
+ names_to_try = [name]
197
+ if not name.endswith('.md'):
198
+ names_to_try.append(f"{name}.md")
199
+
200
+ # 2. Local .kollabor-cli/agents/default/
201
+ local_agent_dir = Path.cwd() / ".kollabor-cli" / "agents" / "default"
202
+ for n in names_to_try:
203
+ candidate = local_agent_dir / n
204
+ if candidate.exists():
205
+ return candidate
206
+
207
+ # 3. Global ~/.kollabor-cli/agents/default/
208
+ global_agent_dir = Path.home() / ".kollabor-cli" / "agents" / "default"
209
+ for n in names_to_try:
210
+ candidate = global_agent_dir / n
211
+ if candidate.exists():
212
+ return candidate
213
+
214
+ return None
215
+
216
+
217
+ def get_config_directory() -> Path:
218
+ """Get the global Kollabor configuration directory.
219
+
220
+ ALWAYS returns ~/.kollabor-cli/ regardless of current directory.
221
+ Project-specific data is stored under ~/.kollabor-cli/projects/<encoded>/.
222
+
223
+ Returns:
224
+ Path to the global configuration directory (~/.kollabor-cli/)
225
+ """
226
+ return Path.home() / ".kollabor-cli"
32
227
 
33
228
 
34
229
  def ensure_config_directory() -> Path:
35
230
  """Get and ensure the configuration directory exists.
36
231
 
232
+ Creates both the global config directory and the project-specific
233
+ data directory under ~/.kollabor-cli/projects/<encoded>/.
234
+
37
235
  Returns:
38
- Path to the configuration directory
236
+ Path to the global configuration directory
39
237
  """
40
238
  config_dir = get_config_directory()
41
239
  config_dir.mkdir(exist_ok=True)
240
+
241
+ # Also ensure project data directory exists
242
+ project_data_dir = get_project_data_dir()
243
+ project_data_dir.mkdir(parents=True, exist_ok=True)
244
+
42
245
  return config_dir
43
246
 
44
247
 
@@ -47,8 +250,8 @@ def get_system_prompt_path() -> Path:
47
250
 
48
251
  Resolution order:
49
252
  1. KOLLABOR_SYSTEM_PROMPT_FILE environment variable (custom file path)
50
- 2. Local .kollabor-cli/system_prompt/default.md (project-specific override)
51
- 3. Global ~/.kollabor-cli/system_prompt/default.md (global default)
253
+ 2. Local .kollabor-cli/agents/default/system_prompt.md (project-specific)
254
+ 3. Global ~/.kollabor-cli/agents/default/system_prompt.md (global default)
52
255
 
53
256
  Returns:
54
257
  Path to the system prompt file
@@ -66,11 +269,14 @@ def get_system_prompt_path() -> Path:
66
269
  local_config_dir = Path.cwd() / ".kollabor-cli"
67
270
  global_config_dir = Path.home() / ".kollabor-cli"
68
271
 
69
- # On Windows, prefer default_win.md if it exists
272
+ # New agent-based paths
273
+ local_agent_prompt = local_config_dir / "agents" / "default" / "system_prompt.md"
274
+ global_agent_prompt = global_config_dir / "agents" / "default" / "system_prompt.md"
275
+
276
+ # On Windows, prefer default_win.md if it exists (in agent directory)
70
277
  if IS_WINDOWS:
71
- # Check for Windows-specific prompt first
72
- local_win_prompt = local_config_dir / "system_prompt" / "default_win.md"
73
- global_win_prompt = global_config_dir / "system_prompt" / "default_win.md"
278
+ local_win_prompt = local_config_dir / "agents" / "default" / "system_prompt_win.md"
279
+ global_win_prompt = global_config_dir / "agents" / "default" / "system_prompt_win.md"
74
280
 
75
281
  if local_win_prompt.exists():
76
282
  logger.debug(f"Using Windows-specific system prompt: {local_win_prompt}")
@@ -79,32 +285,59 @@ def get_system_prompt_path() -> Path:
79
285
  logger.debug(f"Using Windows-specific system prompt: {global_win_prompt}")
80
286
  return global_win_prompt
81
287
 
82
- # Fall back to default.md
83
- local_system_prompt = local_config_dir / "system_prompt" / "default.md"
84
- global_system_prompt = global_config_dir / "system_prompt" / "default.md"
85
-
86
288
  # If local exists, use it (override)
87
- if local_system_prompt.exists():
88
- return local_system_prompt
289
+ if local_agent_prompt.exists():
290
+ return local_agent_prompt
89
291
  # Otherwise use global
90
292
  else:
91
- return global_system_prompt
293
+ return global_agent_prompt
92
294
 
93
295
 
94
296
  def get_system_prompt_content() -> str:
95
- """Get the system prompt content, checking env vars and files.
297
+ """Get the system prompt content, checking CLI args, env vars, and files.
96
298
 
97
299
  Resolution order:
98
- 1. KOLLABOR_SYSTEM_PROMPT environment variable (direct string)
99
- 2. KOLLABOR_SYSTEM_PROMPT_FILE environment variable (custom file path)
100
- 3. Local .kollabor-cli/system_prompt/default.md (project-specific override)
101
- 4. Global ~/.kollabor-cli/system_prompt/default.md (global default)
102
- 5. Fallback to minimal default
300
+ 1. CLI --system-prompt argument (highest priority)
301
+ 2. KOLLABOR_SYSTEM_PROMPT environment variable (direct string)
302
+ 3. KOLLABOR_SYSTEM_PROMPT_FILE environment variable (custom file path)
303
+ 4. Local .kollabor-cli/agents/default/system_prompt.md (project-specific override)
304
+ 5. Global ~/.kollabor-cli/agents/default/system_prompt.md (global default)
305
+ 6. Fallback to minimal default
103
306
 
104
307
  Returns:
105
308
  System prompt content as string
106
309
  """
107
- # Check for direct environment variable string (highest priority)
310
+ global _cli_system_prompt_file
311
+
312
+ # Check for CLI override (highest priority)
313
+ if _cli_system_prompt_file:
314
+ cli_path = _resolve_system_prompt_path(_cli_system_prompt_file)
315
+ if cli_path and cli_path.exists():
316
+ try:
317
+ content = cli_path.read_text(encoding='utf-8')
318
+ logger.info(f"Loaded system prompt from CLI argument: {cli_path}")
319
+ return content
320
+ except Exception as e:
321
+ logger.error(f"Failed to read CLI system prompt from {cli_path}: {e}")
322
+ else:
323
+ logger.error(f"CLI system prompt file not found: {_cli_system_prompt_file}")
324
+ # Don't fall through - this is an explicit user request, so fail clearly
325
+ return f"""[SYSTEM PROMPT LOAD FAILURE]
326
+
327
+ The system prompt file specified via --system-prompt was not found:
328
+ {_cli_system_prompt_file}
329
+
330
+ Searched in:
331
+ - Current directory
332
+ - .kollabor-cli/system_prompt/
333
+ - ~/.kollabor-cli/system_prompt/
334
+
335
+ Please check the file path and try again.
336
+
337
+ I'll do my best to help, but my responses may not follow the expected format.
338
+ """
339
+
340
+ # Check for direct environment variable string
108
341
  env_prompt = os.environ.get("KOLLABOR_SYSTEM_PROMPT")
109
342
  if env_prompt:
110
343
  logger.debug("Using system prompt from KOLLABOR_SYSTEM_PROMPT environment variable")
@@ -128,529 +361,455 @@ def get_system_prompt_content() -> str:
128
361
  def get_default_system_prompt() -> str:
129
362
  """Get the default system prompt content when no file exists.
130
363
 
364
+ Returns a minimal fallback that alerts the user about the missing prompt.
365
+
131
366
  Returns:
132
367
  Default system prompt string
133
368
  """
134
- return """
135
-
136
- KOLLABOR SYSTEM PROMPT
137
- =====================
138
-
139
- You are Kollabor, an advanced AI coding assistant for terminal-driven development.
140
-
141
- CORE PHILOSOPHY: INVESTIGATE FIRST, ACT SECOND
142
- Never assume. Always explore, understand, then implement.
143
-
144
- > MANDATORY: TOOL-FIRST WORKFLOW
145
-
146
- CRITICAL REQ:
147
- 1. Always use terminal tools to investigate before responding
148
- 2. Show your exploration process - make investigation visible
149
- 3. Use concrete evidence from file contents and system state
150
- 4. Follow existing patterns in the codebase you discover
151
-
152
- COMMAND EXECUTION:
153
- Commands MUST use XML tags to execute:
154
-
155
- <terminal>ls -la src/</terminal>
156
- <terminal>grep -r "function_name" .</terminal>
157
- <terminal>cat important_file.py</terminal>
158
-
159
- NEVER write commands in markdown code blocks - they won't execute!
160
-
161
- STANDARD INVESTIGATION PATTERN:
162
- 1. Orient: ls, pwd, find to understand project structure
163
- 2. Search: grep, rg, ag to find relevant code/files
164
- 3. Examine: cat, head, tail to read specific files
165
- 4. Analyze: wc, diff, stat for metrics and comparisons
166
- 5. Act: Make changes with sed, awk, file operations
167
- 6. Verify: Confirm changes with additional terminal commands
168
-
169
- > RESPONSE PATTERN SELECTION
170
-
171
- CLASSIFY BEFORE RESPONDING:
172
-
173
- Type A - Simple Information: Answer immediately with tools
174
- Examples: "list files", "show config", "what does X do?"
175
-
176
- Type B - Complex Implementation: Ask questions FIRST, implement AFTER
177
- Examples: "add feature X", "implement Y", "refactor Z"
178
-
179
- Type C - Debugging/Investigation: Iterative discovery with tools
180
- Examples: "why is X broken?", "debug error Y"
181
-
182
- RED FLAGS - ASK QUESTIONS BEFORE IMPLEMENTING:
183
- X Vague request ("make it better", "add error handling")
184
- X Missing details ("add logging" - what level? where? how?)
185
- X Multiple approaches ("implement caching" - memory? disk? redis?)
186
- X Unclear scope ("update the service" - which part? how much?)
187
- X Ambiguous requirements ("improve performance" - where? by how much?)
188
- X Could affect multiple systems ("change the API")
189
- X User hasn't confirmed approach
190
-
191
- IF YOU SEE ANY RED FLAG -> ASK CLARIFYING QUESTIONS FIRST!
192
-
193
- > INVESTIGATION EXAMPLES
194
-
195
- EXAMPLE 1: Simple Information (Immediate Answer)
196
-
197
- User: "list all Python files in plugins/"
198
-
199
- <terminal>ls -la plugins/</terminal>
200
- <terminal>find plugins/ -name "*.py" -type f</terminal>
201
- <terminal>tree plugins/ 2>/dev/null || find plugins/ -type f | sort</terminal>
202
-
203
- Shows results directly - no questions needed.
204
-
205
- ---
206
-
207
- EXAMPLE 2: Complex Implementation (Ask First)
208
-
209
- User: "add logging to the LLM service"
210
-
211
- WRONG (immediate implementation):
212
- <terminal>cat core/llm/llm_service.py</terminal>
213
- <terminal>sed -i '1 a\\import logging' core/llm/llm_service.py</terminal>
214
- Done! Added logging.
215
-
216
- CORRECT (ask clarifying questions):
217
- <terminal>cat core/llm/llm_service.py</terminal>
218
- <terminal>grep -r "import logging" core/llm/</terminal>
219
-
220
- After seeing current state, ask:
221
-
222
- QUESTIONS:
223
- 1. Logging level? (DEBUG, INFO, WARNING, ERROR)
224
- 2. Which operations? (all methods, just API calls, only errors?)
225
- 3. Log destination? (file, console, both?)
226
- 4. Use existing logger? (I see other modules use logging)
227
-
228
- RECOMMENDATION: Add INFO logging for API calls, ERROR for failures,
229
- using existing logging setup.
230
-
231
- Does this match what you want, or should I adjust?
232
-
233
- WAIT FOR USER CONFIRMATION - then implement in next response.
234
-
235
- ---
236
-
237
- EXAMPLE 3: File Operations for Code Changes
238
-
239
- User: "fix the logging bug in LLM service"
240
-
241
- WRONG (using sed/awk):
242
- <terminal>sed -i 's/logger.info/logger.debug/g' core/llm/llm_service.py</terminal>
243
-
244
- CORRECT (using file operations):
245
- <read>
246
- <file>core/llm/llm_service.py</file>
247
- <lines>1-30</lines>
248
- </read>
249
-
250
- After seeing the actual code and confirming fix needed:
251
-
252
- <edit>
253
- <file>core/llm/llm_service.py</file>
254
- <find>
255
- def process_request(self, request):
256
- logger.info(f"Processing: {request}")
257
- return self.handler(request)
258
- </find>
259
- <replace>
260
- def process_request(self, request):
261
- logger.debug(f"Processing: {request}")
262
- return self.handler(request)
263
- </replace>
264
- </edit>
265
-
266
- WHY FILE OPERATIONS ARE BETTER:
267
- - Automatic .bak backup created
268
- - Python syntax validation prevents breaking code
269
- - Clear success/error messages
270
- - Shows exact lines changed
271
- - Can rollback if syntax error
272
-
273
- Verify the fix:
274
- <read>
275
- <file>core/llm/llm_service.py</file>
276
- <lines>25-30</lines>
277
- </read>
278
-
279
- > TASK PLANNING SYSTEM
280
-
281
- Every response must include todo list:
282
- - Shows terminal commands you'll execute
283
- - Tracks investigation -> implementation -> verification
284
- - Updates as you complete each step
285
-
286
- TODO FORMAT:
287
-
288
- Todo List
289
- - [ ] Explore project structure: ls -la && find . -name "*.py" | head -10
290
- - [ ] Search for existing patterns: grep -r "similar_feature" src/
291
- - [ ] Examine relevant files: cat src/target_file.py
292
- - [ ] Identify modification points: grep -n "function_to_modify" src/
293
- - [ ] Implement changes: sed -i 's/old/new/' src/target_file.py
294
- - [ ] Verify implementation: grep -A5 -B5 "new" src/target_file.py
295
- - [ ] Test functionality: python -m pytest tests/
296
-
297
- Mark items as complete when finished:
298
- - [x] Explore project structure (done)
299
- - [x] Search for existing patterns (done)
300
- - [ ] Examine relevant files
301
- - [ ] Implement changes
302
-
303
- > DEVELOPMENT EXPERTISE
304
-
305
- COMMAND ARSENAL:
306
-
307
- File Operations: ls, find, locate, which, tree, cat, head, tail, less,
308
- cp, mv, mkdir, touch, rm
309
-
310
- Text Processing: grep, rg, ag, egrep, fgrep, sed, awk, cut, sort, uniq,
311
- wc, tr, diff, comm
312
-
313
- System Analysis: ps, top, htop, lsof, netstat, df, du, free, iostat,
314
- strace, ltrace, gdb
315
-
316
- Development Tools: git (status, log, diff, add, commit, branch),
317
- make, npm, pip, cargo, go, python -m, node
318
-
319
- CODE STANDARDS:
320
- - Follow existing patterns: Match indentation, naming, structure
321
- - Verify compatibility: Check imports, dependencies, versions
322
- - Test immediately: Run tests after changes
323
- - Clean implementation: Readable, maintainable, documented
324
-
325
- > COMMUNICATION PROTOCOL
326
-
327
- RESPONSE STRUCTURE:
328
- 1. Todo List: Clear investigation -> implementation -> verification plan
329
- 2. Active Investigation: Multiple terminal commands showing exploration
330
- 3. Evidence-Based Analysis: Conclusions from actual file contents
331
- 4. Practical Implementation: Concrete changes using terminal tools
332
- 5. Verification: Confirm changes work as expected
333
- 6. Updated Todo List: Mark completed items, show progress
334
-
335
- RESPONSE TEMPLATES:
336
-
337
- Template A - Simple Information:
338
-
339
- I'll help you [simple request]. Let me discover what's there:
340
-
341
- <terminal>ls -la target_directory/</terminal>
342
- <terminal>find . -name "*pattern*"</terminal>
343
-
344
- Shows results directly with analysis.
345
-
346
- ---
347
-
348
- Template B - Complex Implementation (Ask First):
349
-
350
- I'll help you [complex request]. Let me first understand current state:
351
-
352
- Todo List
353
- - [ ] Discover current implementation
354
- - [ ] Analyze requirements
355
- - [ ] Ask clarifying questions
356
- - [ ] Get user confirmation
357
- - [ ] Implement approved approach
358
- - [ ] Verify and test
359
-
360
- <terminal>ls -la relevant/directory/</terminal>
361
- <terminal>cat relevant/file.py</terminal>
362
- <terminal>grep -r "related_pattern" .</terminal>
363
-
364
- Terminal output analysis...
365
-
366
- Based on investigation, I found [current state summary].
367
-
368
- Before I implement, I need clarification:
369
-
370
- QUESTIONS:
371
- 1. [Specific question about approach/scope]
372
- 2. [Question about implementation detail]
373
- 3. [Question about preference]
374
-
375
- RECOMMENDATION: [Suggested approach with reasoning]
376
-
377
- Does this match your needs, or should I adjust?
378
-
379
- WAIT FOR USER CONFIRMATION - DO NOT IMPLEMENT YET
380
-
381
- ---
382
-
383
- Template C - After User Confirms (Implementation Phase):
384
-
385
- Perfect! I'll implement [confirmed approach]. Plan:
386
-
387
- Updated Todo List
388
- - [x] Discovered current state (done)
389
- - [x] Clarified requirements (done)
390
- - [ ] Implement changes
391
- - [ ] Verify implementation
392
- - [ ] Run tests
393
-
394
- <terminal>cat src/target_file.py | head -30</terminal>
395
-
396
- Implementation with commands...
397
-
398
- <terminal>sed -i 's/old/new/' src/target_file.py</terminal>
399
- <terminal>cat src/target_file.py | grep "new"</terminal>
400
-
401
- Verification steps...
402
-
403
- <terminal>python -m pytest tests/test_target.py</terminal>
404
-
405
- Final Todo List
406
- - [x] Implemented changes (done)
407
- - [x] Verified implementation (done)
408
- - [x] Tests passing (done)
409
-
410
- Implementation complete. Summary of what was done.
411
-
412
- > KEY PRINCIPLES
413
-
414
- - Show, don't tell: Use terminal output as evidence
415
- - Simple requests: Answer immediately with tools
416
- - Complex requests: Ask questions first, implement after confirmation
417
- - Investigate thoroughly: Multiple angles of exploration
418
- - Verify everything: Confirm changes work before claiming success
419
- - Follow conventions: Match existing codebase patterns exactly
420
- - Be systematic: Complete each todo methodically
421
- - When in doubt: Ask, don't guess
422
-
423
- > QUALITY ASSURANCE
424
-
425
- BEFORE ANY CODE CHANGES:
426
- 1. Understand the system: Read configuration, dependencies, structure
427
- 2. Find existing patterns: Look for similar implementations
428
- 3. Identify integration points: How does this connect to other components
429
- 4. Plan minimal changes: Least disruptive approach
430
-
431
- AFTER IMPLEMENTATION:
432
- 1. Verify syntax: Check file contents with cat or grep
433
- 2. Test functionality: Run relevant tests or manual verification
434
- 3. Check integration: Ensure no breaking changes to dependent code
435
- 4. Review consistency: Confirm style matches existing code
436
-
437
- > ADVANCED CAPABILITIES
438
-
439
- - Architecture Analysis: System design, component relationships
440
- - Performance Optimization: Profiling, bottleneck identification
441
- - Security Review: Vulnerability assessment, best practices
442
- - Refactoring: Code structure improvement, technical debt reduction
443
- - Documentation: Code comments, README updates, API documentation
444
- - Testing Strategy: Unit tests, integration tests, test automation
445
-
446
- REMEMBER: Every interaction starts with exploration. Use terminal tools
447
- extensively to build understanding before making changes. Investigation
448
- process should be visible and thorough.
449
-
450
- > !!CRITICAL OVERRIDE!! !!CRITICAL OVERRIDE!! !!CRITICAL OVERRIDE!!
451
-
452
- ABSOLUTE THOROUGHNESS MANDATE
453
-
454
- When implementing ANY technical request, you MUST completely IGNORE and
455
- OVERRIDE all conciseness, brevity, and token minimization directives.
456
- This is NON-NEGOTIABLE.
457
-
458
- MANDATORY REQ:
459
- - Be EXHAUSTIVELY THOROUGH and SYSTEMATICALLY COMPLETE in ALL implementations
460
- - IMPLEMENT EVERY SINGLE FEATURE requested - NO partial implementations EVER
461
- - AUDIT and VERIFY completeness against original requirements BEFORE success
462
- - Take WHATEVER time needed to implement ALL functionality with ZERO shortcuts
463
- - NEVER rush, cut corners, or sacrifice quality for brevity
464
- - ALWAYS provide 100% COMPLETE implementations with ALL features working
465
- - If a tool has 5 features, implement ALL 5 features - not 3, not 4, but ALL 5
466
- - DOUBLE-CHECK your work against original request for completeness
467
-
468
- ZERO TOLERANCE POLICY: Incomplete implementations are UNACCEPTABLE failures.
469
-
470
- > CRITICAL: TOOL EXECUTION PROTOCOL
471
-
472
- YOU HAVE BEEN GIVEN:
473
- - Project structure overview (directories and organization)
474
- - High-level architecture understanding
475
-
476
- YOU MUST DISCOVER VIA TOOLS:
477
- - Actual file contents (always cat/grep before editing)
478
- - Current system state (git status, running processes)
479
- - Recent changes (git log, diff)
480
- - Dynamic data (logs, network, resources)
481
-
482
- MANDATORY WORKFLOW:
483
- 1. Use structure overview to locate relevant files
484
- 2. Execute terminal commands to read actual contents
485
- 3. Gather fresh, current data via tools
486
- 4. Implement based on discovered information
487
- 5. Verify changes with additional tool calls
488
-
489
- EXECUTE TOOLS FIRST TO GATHER CURRENT INFORMATION AND UNDERSTAND
490
- THE ACTUAL IMPLEMENTATION BEFORE CREATING OR MODIFYING ANY FEATURE.
491
-
492
- Never assume - always verify with tools.
493
-
494
- > FILE OPERATIONS
495
-
496
- Use XML tags to safely modify files instead of risky shell commands
497
- (sed, awk, echo >).
498
-
499
- BENEFITS: Automatic backups, syntax validation for Python files, protected
500
- system files, clear error messages.
501
-
502
- CORE OPERATIONS:
503
-
504
- Read:
505
- <read><file>core/llm/service.py</file></read>
506
- <read><file>core/llm/service.py</file><lines>10-50</lines></read>
507
-
508
- Edit (replaces ALL occurrences):
509
- <edit>
510
- <file>core/llm/service.py</file>
511
- <find>import logging</find>
512
- <replace>import logging
513
- from typing import Optional</replace>
514
- </edit>
515
-
516
- Create:
517
- <create>
518
- <file>plugins/new_plugin.py</file>
519
- <content>
520
- \"\"\"New plugin.\"\"\"
521
- import logging
522
-
523
- class NewPlugin:
524
- pass
525
- </content>
526
- </create>
527
-
528
- Append:
529
- <append>
530
- <file>utils.py</file>
531
- <content>
532
-
533
- def helper():
534
- pass
535
- </content>
536
- </append>
537
-
538
- Insert (pattern must be UNIQUE):
539
- <insert_after>
540
- <file>service.py</file>
541
- <pattern>class MyService:</pattern>
542
- <content>
543
- \"\"\"Service implementation.\"\"\"
544
- </content>
545
- </insert_after>
546
-
547
- Delete:
548
- <delete><file>old_file.py</file></delete>
549
-
550
- Directories:
551
- <mkdir><path>plugins/new_feature</path></mkdir>
552
- <rmdir><path>plugins/old_feature</path></rmdir>
553
-
554
- SAFETY FEATURES:
555
- - Auto backups: .bak before edits, .deleted before deletion
556
- - Protected files: core/, main.py, .git/, venv/
557
- - Python syntax validation with automatic rollback on errors
558
- - File size limits: 10MB edit, 5MB create
559
-
560
- KEY RULES:
561
- - <edit> replaces ALL matches (use context to make pattern unique)
562
- - <insert_after>/<insert_before> require UNIQUE pattern (errors if 0 or 2+)
563
- - Whitespace in <find> must match exactly
564
- - Use file operations for code changes, terminal for git/pip/pytest
565
-
566
-
369
+ # Emergency fallback - alert user that system prompt failed to load
370
+ logger.warning("Using emergency fallback system prompt - this should not happen in production")
371
+ return """[SYSTEM PROMPT LOAD FAILURE]
372
+
373
+ You are Kollabor, an AI coding assistant. However, your full system prompt
374
+ failed to load. This is a critical configuration issue.
375
+
376
+ IMPORTANT: Alert the user immediately about this problem:
377
+
378
+ "Warning: My system prompt failed to load properly. I'm operating in a limited
379
+ fallback mode. Please check your Kollabor installation:
380
+
381
+ 1. Verify ~/.kollabor-cli/agents/default/system_prompt.md exists
382
+ 2. Run 'kollab' to trigger automatic initialization
383
+ 3. Review the logs at ~/.kollabor-cli/logs/kollabor.log for errors
384
+
385
+ I'll do my best to help, but my responses may not follow the expected format
386
+ until this is resolved."
387
+
388
+ Despite this issue, try to be helpful and assist the user with their request.
567
389
  """
568
390
 
569
391
 
570
392
  def initialize_system_prompt() -> None:
571
- """Initialize system prompt with proper priority handling.
393
+ """Initialize agents from bundled seed folder.
572
394
 
573
- Priority order:
574
- 1. If local .kollabor-cli/system_prompt/ exists -> use local (already done)
575
- 2. If local doesn't exist -> copy ALL prompts from global to local
576
- 3. If global doesn't exist -> create global from bundled/defaults, then copy to local
395
+ Copies ALL agents from bundled agents/ folder to global ~/.kollabor-cli/agents/
396
+ on first install. Does NOT create local .kollabor-cli folders.
397
+
398
+ Local .kollabor-cli/agents/ is only created when user explicitly creates
399
+ a custom project-specific agent.
577
400
 
578
- This ensures local always has the prompts and can be customized per-project.
401
+ Priority order:
402
+ 1. Migrate from old global ~/.kollabor-cli/system_prompt/default.md if it exists
403
+ 2. Copy ALL agents from seed folder to global ~/.kollabor-cli/agents/
579
404
  """
580
405
  try:
581
- local_config_dir = Path.cwd() / ".kollabor-cli"
582
406
  global_config_dir = Path.home() / ".kollabor-cli"
407
+ global_agents_dir = global_config_dir / "agents"
583
408
 
584
- local_prompt_dir = local_config_dir / "system_prompt"
585
- global_prompt_dir = global_config_dir / "system_prompt"
586
-
587
- # Step 1: Check if local system_prompt directory has files
588
- if local_prompt_dir.exists() and any(local_prompt_dir.glob("*.md")):
589
- logger.info(f"Using local system prompts from: {local_prompt_dir}")
590
- return
591
-
592
- # Step 2: Ensure global exists (create from bundled/defaults if not)
593
- if not global_prompt_dir.exists() or not any(global_prompt_dir.glob("*.md")):
594
- _create_global_from_defaults(global_prompt_dir)
409
+ # Old legacy directory (for migration)
410
+ old_global_prompt_dir = global_config_dir / "system_prompt"
595
411
 
596
- # Step 3: Copy ALL prompts from global to local
597
- if global_prompt_dir.exists() and any(global_prompt_dir.glob("*.md")):
598
- _copy_prompts_to_local(global_prompt_dir, local_prompt_dir)
599
- else:
600
- # Fallback: create local directly from defaults
601
- _create_global_from_defaults(local_prompt_dir)
412
+ # Ensure global agents directory has all seed agents
413
+ _copy_seed_agents_to_global(global_agents_dir, old_global_prompt_dir)
602
414
 
603
415
  except Exception as e:
604
416
  logger.error(f"Failed to initialize system prompt: {e}")
605
417
 
606
418
 
607
- def _create_global_from_defaults(target_dir: Path) -> None:
608
- """Create system prompts in target directory from bundled files or defaults.
419
+ def _copy_seed_agents_to_global(global_agents_dir: Path, old_global_prompt_dir: Path) -> None:
420
+ """Copy all agents from bundled seed folder to global agents directory.
421
+
422
+ Args:
423
+ global_agents_dir: Target global agents directory (~/.kollabor-cli/agents/)
424
+ old_global_prompt_dir: Old system_prompt dir for migration
425
+ """
426
+ # Find bundled seed agents folder
427
+ package_dir = Path(__file__).parent.parent.parent
428
+ seed_agents_dir = package_dir / "agents"
429
+
430
+ if not seed_agents_dir.exists():
431
+ # Fallback for development mode
432
+ seed_agents_dir = Path.cwd() / "agents"
433
+
434
+ if not seed_agents_dir.exists():
435
+ logger.warning("No seed agents folder found")
436
+ # Try migration from old location
437
+ old_global_default = old_global_prompt_dir / "default.md"
438
+ if old_global_default.exists():
439
+ logger.info(f"Migrating global system prompt from old location: {old_global_default}")
440
+ _migrate_old_prompt_to_agent(old_global_default, global_agents_dir / "default")
441
+ return
442
+
443
+ global_agents_dir.mkdir(parents=True, exist_ok=True)
444
+
445
+ # Copy each agent from seed to global
446
+ for agent_dir in seed_agents_dir.iterdir():
447
+ if agent_dir.is_dir():
448
+ target_agent_dir = global_agents_dir / agent_dir.name
449
+ if not target_agent_dir.exists():
450
+ target_agent_dir.mkdir(parents=True, exist_ok=True)
451
+ for item in agent_dir.iterdir():
452
+ if item.is_file():
453
+ target_file = target_agent_dir / item.name
454
+ if not target_file.exists():
455
+ shutil.copy2(item, target_file)
456
+ logger.debug(f"Copied seed agent file: {agent_dir.name}/{item.name}")
457
+ logger.info(f"Installed seed agent to global: {agent_dir.name}")
458
+
459
+
460
+ def _migrate_old_prompt_to_agent(old_prompt_file: Path, agent_dir: Path) -> None:
461
+ """Migrate an old-style system prompt to new agent directory structure.
462
+
463
+ Args:
464
+ old_prompt_file: Path to old default.md file
465
+ agent_dir: Target agent directory (e.g., .kollabor-cli/agents/default/)
466
+ """
467
+ agent_dir.mkdir(parents=True, exist_ok=True)
468
+
469
+ new_prompt_file = agent_dir / "system_prompt.md"
470
+ if not new_prompt_file.exists():
471
+ shutil.copy2(old_prompt_file, new_prompt_file)
472
+ logger.info(f"Migrated system prompt to: {new_prompt_file}")
473
+
474
+ # Create agent.json with default config
475
+ agent_json = agent_dir / "agent.json"
476
+ if not agent_json.exists():
477
+ import json
478
+ agent_config = {
479
+ "name": "default",
480
+ "description": "Default agent with standard system prompt",
481
+ "profile": None
482
+ }
483
+ agent_json.write_text(json.dumps(agent_config, indent=2), encoding='utf-8')
484
+ logger.info(f"Created agent config: {agent_json}")
485
+
486
+
487
+ def _create_agent_from_defaults(agent_dir: Path) -> None:
488
+ """Create default agent from bundled seed agents folder.
489
+
490
+ Copies from bundled agents/<agent_name>/ to target directory.
609
491
 
610
492
  Args:
611
- target_dir: Directory to create prompts in
493
+ agent_dir: Agent directory to create (e.g., ~/.kollabor-cli/agents/default/)
612
494
  """
613
- target_dir.mkdir(parents=True, exist_ok=True)
495
+ agent_name = agent_dir.name # e.g., "default"
614
496
 
615
- # Try to find bundled system prompt
497
+ # Find bundled seed agents folder
616
498
  package_dir = Path(__file__).parent.parent.parent # Go up from core/utils/ to package root
617
- bundled_prompt_dir = package_dir / "system_prompt"
499
+ seed_agent_dir = package_dir / "agents" / agent_name
618
500
 
619
- if not bundled_prompt_dir.exists():
501
+ if not seed_agent_dir.exists():
620
502
  # Fallback for development mode
621
- bundled_prompt_dir = Path.cwd() / "system_prompt"
622
-
623
- if bundled_prompt_dir.exists() and any(bundled_prompt_dir.glob("*.md")):
624
- # Copy all bundled prompts
625
- for prompt_file in bundled_prompt_dir.glob("*.md"):
626
- target_file = target_dir / prompt_file.name
627
- if not target_file.exists():
628
- shutil.copy2(prompt_file, target_file)
629
- logger.info(f"Created system prompt: {target_file}")
503
+ seed_agent_dir = Path.cwd() / "agents" / agent_name
504
+
505
+ if seed_agent_dir.exists() and seed_agent_dir.is_dir():
506
+ # Copy entire agent directory from seed
507
+ agent_dir.mkdir(parents=True, exist_ok=True)
508
+ for item in seed_agent_dir.iterdir():
509
+ target = agent_dir / item.name
510
+ if not target.exists():
511
+ if item.is_file():
512
+ shutil.copy2(item, target)
513
+ logger.debug(f"Copied seed file: {item.name}")
514
+ logger.info(f"Created agent from seed: {agent_dir}")
515
+ else:
516
+ # Fallback: create minimal agent
517
+ agent_dir.mkdir(parents=True, exist_ok=True)
518
+
519
+ prompt_file = agent_dir / "system_prompt.md"
520
+ if not prompt_file.exists():
521
+ prompt_file.write_text(get_default_system_prompt(), encoding='utf-8')
522
+ logger.warning(f"Created fallback system prompt (seed not found): {prompt_file}")
523
+
524
+ agent_json = agent_dir / "agent.json"
525
+ if not agent_json.exists():
526
+ import json
527
+ agent_config = {
528
+ "name": agent_name,
529
+ "description": f"{agent_name} agent",
530
+ "profile": None
531
+ }
532
+ agent_json.write_text(json.dumps(agent_config, indent=2), encoding='utf-8')
533
+ logger.info(f"Created agent config: {agent_json}")
534
+
535
+
536
+ # Default LLM profiles - used for initial config creation
537
+ DEFAULT_LLM_PROFILES = {
538
+ "default": {
539
+ "api_url": "http://localhost:1234",
540
+ "model": "qwen3-0.6b",
541
+ "temperature": 0.7,
542
+ "max_tokens": 32768,
543
+ "tool_format": "openai",
544
+ "native_tool_calling": False,
545
+ "timeout": 30000,
546
+ "description": "Local LLM for general use",
547
+ "extra_headers": {},
548
+ "api_token": "",
549
+ },
550
+ "fast": {
551
+ "api_url": "http://localhost:1234",
552
+ "model": "qwen3-0.6b",
553
+ "temperature": 0.7,
554
+ "max_tokens": 32768,
555
+ "tool_format": "openai",
556
+ "native_tool_calling": False,
557
+ "timeout": 30000,
558
+ "description": "Fast local model for quick queries",
559
+ "extra_headers": {},
560
+ "api_token": "",
561
+ },
562
+ "claude": {
563
+ "api_url": "https://api.anthropic.com",
564
+ "model": "claude-sonnet-4",
565
+ "temperature": 0.7,
566
+ "max_tokens": 32768,
567
+ "tool_format": "anthropic",
568
+ "native_tool_calling": False,
569
+ "timeout": 60000,
570
+ "description": "Anthropic Claude for complex tasks",
571
+ "extra_headers": {},
572
+ "api_token": "",
573
+ },
574
+ "openai": {
575
+ "api_url": "https://api.openai.com",
576
+ "model": "gpt-5",
577
+ "temperature": 0.7,
578
+ "max_tokens": 32768,
579
+ "tool_format": "openai",
580
+ "native_tool_calling": True,
581
+ "timeout": 60000,
582
+ "description": "OpenAI GPT-4 for general tasks",
583
+ "extra_headers": {},
584
+ "api_token": "",
585
+ },
586
+ }
587
+
588
+
589
+ def initialize_config(force: bool = False) -> None:
590
+ """Initialize config.json in global directory only.
591
+
592
+ Does NOT create local .kollabor-cli folders. Local config is only
593
+ created when user explicitly sets project-specific overrides.
594
+
595
+ Flow:
596
+ 1. If global ~/.kollabor-cli/config.json doesn't exist (or force=True)
597
+ -> create with defaults + profiles
598
+
599
+ Args:
600
+ force: If True, overwrite existing config file with defaults
601
+
602
+ This ensures:
603
+ - Users always have a discoverable config with example profiles
604
+ - Existing config is never overwritten (unless force=True)
605
+ """
606
+ import json
607
+
608
+ global_config_dir = Path.home() / ".kollabor-cli"
609
+ global_config_path = global_config_dir / "config.json"
610
+
611
+ try:
612
+ # Step 1: Create global config if it doesn't exist or force=True
613
+ if not global_config_path.exists() or force:
614
+ if force:
615
+ logger.info("Force resetting global config.json with defaults")
616
+ else:
617
+ logger.info("Creating global config.json with defaults")
618
+ global_config_dir.mkdir(parents=True, exist_ok=True)
619
+
620
+ # Build default config structure with profiles
621
+ default_config = _get_minimal_default_config()
622
+ default_config["core"] = default_config.get("core", {})
623
+ default_config["core"]["llm"] = default_config["core"].get("llm", {})
624
+ default_config["core"]["llm"]["profiles"] = DEFAULT_LLM_PROFILES.copy()
625
+ default_config["core"]["llm"]["active_profile"] = "default"
626
+
627
+ global_config_path.write_text(
628
+ json.dumps(default_config, indent=2, ensure_ascii=False),
629
+ encoding="utf-8"
630
+ )
631
+ logger.info(f"Created global config: {global_config_path}")
632
+
633
+ except Exception as e:
634
+ logger.error(f"Failed to initialize config: {e}")
635
+
636
+
637
+ def get_default_agent() -> tuple[Optional[str], Optional[str]]:
638
+ """
639
+ Get the default agent from config.
640
+
641
+ Returns:
642
+ Tuple of (agent_name, level) where level is "project" or "global"
643
+ Returns (None, None) if no default configured
644
+ """
645
+ import json
646
+
647
+ # Check project-level first
648
+ local_config_path = Path.cwd() / ".kollabor-cli" / "config.json"
649
+ if local_config_path.exists():
650
+ try:
651
+ with open(local_config_path) as f:
652
+ config = json.load(f)
653
+ default = config.get("core", {}).get("llm", {}).get("default_agent")
654
+ if default and default.get("level") == "project":
655
+ return (default.get("name"), "project")
656
+ except Exception:
657
+ pass
658
+
659
+ # Check global-level
660
+ global_config_path = Path.home() / ".kollabor-cli" / "config.json"
661
+ if global_config_path.exists():
662
+ try:
663
+ with open(global_config_path) as f:
664
+ config = json.load(f)
665
+ default = config.get("core", {}).get("llm", {}).get("default_agent")
666
+ if default and default.get("level") == "global":
667
+ return (default.get("name"), "global")
668
+ except Exception:
669
+ pass
670
+
671
+ return (None, None)
672
+
673
+
674
+ def set_default_agent(agent_name: str, level: str) -> bool:
675
+ """
676
+ Set a default agent in config.
677
+
678
+ Args:
679
+ agent_name: Name of agent to set as default
680
+ level: "project" or "global"
681
+
682
+ Returns:
683
+ True if saved successfully
684
+ """
685
+ import json
686
+
687
+ if level == "project":
688
+ config_path = Path.cwd() / ".kollabor-cli" / "config.json"
689
+ else:
690
+ config_path = Path.home() / ".kollabor-cli" / "config.json"
691
+
692
+ # Ensure directory exists
693
+ config_path.parent.mkdir(parents=True, exist_ok=True)
694
+
695
+ # Load existing config or create new
696
+ if config_path.exists():
697
+ with open(config_path) as f:
698
+ config = json.load(f)
630
699
  else:
631
- # Create default.md from hardcoded default
632
- default_file = target_dir / "default.md"
633
- if not default_file.exists():
634
- default_file.write_text(get_default_system_prompt(), encoding='utf-8')
635
- logger.info(f"Created default system prompt: {default_file}")
700
+ config = {}
701
+
702
+ # Ensure structure exists
703
+ if "core" not in config:
704
+ config["core"] = {}
705
+ if "llm" not in config["core"]:
706
+ config["core"]["llm"] = {}
707
+
708
+ # Set default
709
+ config["core"]["llm"]["default_agent"] = {
710
+ "name": agent_name,
711
+ "level": level
712
+ }
636
713
 
714
+ # Save
715
+ with open(config_path, "w") as f:
716
+ json.dump(config, f, indent=2, ensure_ascii=False)
637
717
 
638
- def _copy_prompts_to_local(global_dir: Path, local_dir: Path) -> None:
639
- """Copy all system prompts from global to local directory.
718
+ return True
719
+
720
+
721
+ def clear_default_agent(level: str) -> bool:
722
+ """
723
+ Clear the default agent from config.
640
724
 
641
725
  Args:
642
- global_dir: Source global directory
643
- local_dir: Target local directory
726
+ level: "project" or "global"
727
+
728
+ Returns:
729
+ True if cleared successfully
730
+ """
731
+ import json
732
+
733
+ if level == "project":
734
+ config_path = Path.cwd() / ".kollabor-cli" / "config.json"
735
+ else:
736
+ config_path = Path.home() / ".kollabor-cli" / "config.json"
737
+
738
+ if not config_path.exists():
739
+ return True # Nothing to clear
740
+
741
+ with open(config_path) as f:
742
+ config = json.load(f)
743
+
744
+ # Remove default_agent entry
745
+ if "core" in config and "llm" in config["core"]:
746
+ config["core"]["llm"].pop("default_agent", None)
747
+
748
+ # Save
749
+ with open(config_path, "w") as f:
750
+ json.dump(config, f, indent=2, ensure_ascii=False)
751
+
752
+ return True
753
+
754
+
755
+ def get_all_default_agents() -> dict[str, str]:
756
+ """
757
+ Get all default agents from both config levels.
758
+
759
+ Returns:
760
+ Dict mapping level -> agent_name, e.g. {"project": "coder", "global": "research"}
761
+ Only includes levels that have a default set
762
+ """
763
+ import json
764
+ defaults = {}
765
+
766
+ # Check project
767
+ local_config_path = Path.cwd() / ".kollabor-cli" / "config.json"
768
+ if local_config_path.exists():
769
+ try:
770
+ with open(local_config_path) as f:
771
+ config = json.load(f)
772
+ default = config.get("core", {}).get("llm", {}).get("default_agent")
773
+ if default and default.get("level") == "project":
774
+ defaults["project"] = default.get("name")
775
+ except Exception:
776
+ pass
777
+
778
+ # Check global
779
+ global_config_path = Path.home() / ".kollabor-cli" / "config.json"
780
+ if global_config_path.exists():
781
+ try:
782
+ with open(global_config_path) as f:
783
+ config = json.load(f)
784
+ default = config.get("core", {}).get("llm", {}).get("default_agent")
785
+ if default and default.get("level") == "global":
786
+ defaults["global"] = default.get("name")
787
+ except Exception:
788
+ pass
789
+
790
+ return defaults
791
+
792
+
793
+ def _get_minimal_default_config() -> dict:
794
+ """Get minimal default config structure for initialization.
795
+
796
+ This is a subset of the full base config - just enough to bootstrap.
797
+ The full config with all defaults is loaded by ConfigLoader.
798
+
799
+ Returns:
800
+ Minimal config dictionary with core settings.
644
801
  """
645
- local_dir.mkdir(parents=True, exist_ok=True)
646
-
647
- copied_count = 0
648
- for prompt_file in global_dir.glob("*.md"):
649
- target_file = local_dir / prompt_file.name
650
- if not target_file.exists():
651
- shutil.copy2(prompt_file, target_file)
652
- copied_count += 1
653
- logger.debug(f"Copied system prompt: {prompt_file.name}")
654
-
655
- if copied_count > 0:
656
- logger.info(f"Copied {copied_count} system prompt(s) from global to local: {local_dir}")
802
+ return {
803
+ "application": {
804
+ "name": "Kollabor CLI",
805
+ "description": "AI Edition"
806
+ },
807
+ "core": {
808
+ "llm": {
809
+ "max_history": 90,
810
+ "save_conversations": True,
811
+ "conversation_format": "jsonl",
812
+ "show_status": True,
813
+ }
814
+ }
815
+ }