code-puppy 0.0.214__py3-none-any.whl → 0.0.366__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 (231) hide show
  1. code_puppy/__init__.py +7 -1
  2. code_puppy/agents/__init__.py +2 -0
  3. code_puppy/agents/agent_c_reviewer.py +59 -6
  4. code_puppy/agents/agent_code_puppy.py +7 -1
  5. code_puppy/agents/agent_code_reviewer.py +12 -2
  6. code_puppy/agents/agent_cpp_reviewer.py +73 -6
  7. code_puppy/agents/agent_creator_agent.py +45 -4
  8. code_puppy/agents/agent_golang_reviewer.py +92 -3
  9. code_puppy/agents/agent_javascript_reviewer.py +101 -8
  10. code_puppy/agents/agent_manager.py +81 -4
  11. code_puppy/agents/agent_pack_leader.py +383 -0
  12. code_puppy/agents/agent_planning.py +163 -0
  13. code_puppy/agents/agent_python_programmer.py +165 -0
  14. code_puppy/agents/agent_python_reviewer.py +28 -6
  15. code_puppy/agents/agent_qa_expert.py +98 -6
  16. code_puppy/agents/agent_qa_kitten.py +12 -7
  17. code_puppy/agents/agent_security_auditor.py +113 -3
  18. code_puppy/agents/agent_terminal_qa.py +323 -0
  19. code_puppy/agents/agent_typescript_reviewer.py +106 -7
  20. code_puppy/agents/base_agent.py +802 -176
  21. code_puppy/agents/event_stream_handler.py +350 -0
  22. code_puppy/agents/pack/__init__.py +34 -0
  23. code_puppy/agents/pack/bloodhound.py +304 -0
  24. code_puppy/agents/pack/husky.py +321 -0
  25. code_puppy/agents/pack/retriever.py +393 -0
  26. code_puppy/agents/pack/shepherd.py +348 -0
  27. code_puppy/agents/pack/terrier.py +287 -0
  28. code_puppy/agents/pack/watchdog.py +367 -0
  29. code_puppy/agents/prompt_reviewer.py +145 -0
  30. code_puppy/agents/subagent_stream_handler.py +276 -0
  31. code_puppy/api/__init__.py +13 -0
  32. code_puppy/api/app.py +169 -0
  33. code_puppy/api/main.py +21 -0
  34. code_puppy/api/pty_manager.py +446 -0
  35. code_puppy/api/routers/__init__.py +12 -0
  36. code_puppy/api/routers/agents.py +36 -0
  37. code_puppy/api/routers/commands.py +217 -0
  38. code_puppy/api/routers/config.py +74 -0
  39. code_puppy/api/routers/sessions.py +232 -0
  40. code_puppy/api/templates/terminal.html +361 -0
  41. code_puppy/api/websocket.py +154 -0
  42. code_puppy/callbacks.py +142 -4
  43. code_puppy/chatgpt_codex_client.py +283 -0
  44. code_puppy/claude_cache_client.py +586 -0
  45. code_puppy/cli_runner.py +916 -0
  46. code_puppy/command_line/add_model_menu.py +1079 -0
  47. code_puppy/command_line/agent_menu.py +395 -0
  48. code_puppy/command_line/attachments.py +10 -5
  49. code_puppy/command_line/autosave_menu.py +605 -0
  50. code_puppy/command_line/clipboard.py +527 -0
  51. code_puppy/command_line/colors_menu.py +520 -0
  52. code_puppy/command_line/command_handler.py +176 -738
  53. code_puppy/command_line/command_registry.py +150 -0
  54. code_puppy/command_line/config_commands.py +715 -0
  55. code_puppy/command_line/core_commands.py +792 -0
  56. code_puppy/command_line/diff_menu.py +863 -0
  57. code_puppy/command_line/load_context_completion.py +15 -22
  58. code_puppy/command_line/mcp/base.py +0 -3
  59. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  60. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  61. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  62. code_puppy/command_line/mcp/edit_command.py +148 -0
  63. code_puppy/command_line/mcp/handler.py +9 -4
  64. code_puppy/command_line/mcp/help_command.py +6 -5
  65. code_puppy/command_line/mcp/install_command.py +15 -26
  66. code_puppy/command_line/mcp/install_menu.py +685 -0
  67. code_puppy/command_line/mcp/list_command.py +2 -2
  68. code_puppy/command_line/mcp/logs_command.py +174 -65
  69. code_puppy/command_line/mcp/remove_command.py +2 -2
  70. code_puppy/command_line/mcp/restart_command.py +12 -4
  71. code_puppy/command_line/mcp/search_command.py +16 -10
  72. code_puppy/command_line/mcp/start_all_command.py +18 -6
  73. code_puppy/command_line/mcp/start_command.py +47 -25
  74. code_puppy/command_line/mcp/status_command.py +4 -5
  75. code_puppy/command_line/mcp/stop_all_command.py +7 -1
  76. code_puppy/command_line/mcp/stop_command.py +8 -4
  77. code_puppy/command_line/mcp/test_command.py +2 -2
  78. code_puppy/command_line/mcp/wizard_utils.py +20 -16
  79. code_puppy/command_line/mcp_completion.py +174 -0
  80. code_puppy/command_line/model_picker_completion.py +75 -25
  81. code_puppy/command_line/model_settings_menu.py +884 -0
  82. code_puppy/command_line/motd.py +14 -8
  83. code_puppy/command_line/onboarding_slides.py +179 -0
  84. code_puppy/command_line/onboarding_wizard.py +340 -0
  85. code_puppy/command_line/pin_command_completion.py +329 -0
  86. code_puppy/command_line/prompt_toolkit_completion.py +463 -63
  87. code_puppy/command_line/session_commands.py +296 -0
  88. code_puppy/command_line/utils.py +54 -0
  89. code_puppy/config.py +898 -112
  90. code_puppy/error_logging.py +118 -0
  91. code_puppy/gemini_code_assist.py +385 -0
  92. code_puppy/gemini_model.py +602 -0
  93. code_puppy/http_utils.py +210 -148
  94. code_puppy/keymap.py +128 -0
  95. code_puppy/main.py +5 -698
  96. code_puppy/mcp_/__init__.py +17 -0
  97. code_puppy/mcp_/async_lifecycle.py +35 -4
  98. code_puppy/mcp_/blocking_startup.py +70 -43
  99. code_puppy/mcp_/captured_stdio_server.py +2 -2
  100. code_puppy/mcp_/config_wizard.py +4 -4
  101. code_puppy/mcp_/dashboard.py +15 -6
  102. code_puppy/mcp_/managed_server.py +65 -38
  103. code_puppy/mcp_/manager.py +146 -52
  104. code_puppy/mcp_/mcp_logs.py +224 -0
  105. code_puppy/mcp_/registry.py +6 -6
  106. code_puppy/mcp_/server_registry_catalog.py +24 -5
  107. code_puppy/messaging/__init__.py +199 -2
  108. code_puppy/messaging/bus.py +610 -0
  109. code_puppy/messaging/commands.py +167 -0
  110. code_puppy/messaging/markdown_patches.py +57 -0
  111. code_puppy/messaging/message_queue.py +17 -48
  112. code_puppy/messaging/messages.py +500 -0
  113. code_puppy/messaging/queue_console.py +1 -24
  114. code_puppy/messaging/renderers.py +43 -146
  115. code_puppy/messaging/rich_renderer.py +1027 -0
  116. code_puppy/messaging/spinner/__init__.py +21 -5
  117. code_puppy/messaging/spinner/console_spinner.py +86 -51
  118. code_puppy/messaging/subagent_console.py +461 -0
  119. code_puppy/model_factory.py +634 -83
  120. code_puppy/model_utils.py +167 -0
  121. code_puppy/models.json +66 -68
  122. code_puppy/models_dev_api.json +1 -0
  123. code_puppy/models_dev_parser.py +592 -0
  124. code_puppy/plugins/__init__.py +164 -10
  125. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  126. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  127. code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
  128. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  129. code_puppy/plugins/antigravity_oauth/constants.py +136 -0
  130. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  131. code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
  132. code_puppy/plugins/antigravity_oauth/storage.py +271 -0
  133. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  134. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  135. code_puppy/plugins/antigravity_oauth/transport.py +767 -0
  136. code_puppy/plugins/antigravity_oauth/utils.py +169 -0
  137. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  138. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  139. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  140. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
  141. code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
  142. code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
  143. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  144. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  145. code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
  146. code_puppy/plugins/claude_code_oauth/config.py +50 -0
  147. code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
  148. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  149. code_puppy/plugins/claude_code_oauth/utils.py +518 -0
  150. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  151. code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
  152. code_puppy/plugins/example_custom_command/README.md +280 -0
  153. code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
  154. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  155. code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
  156. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  157. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  158. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  159. code_puppy/plugins/oauth_puppy_html.py +228 -0
  160. code_puppy/plugins/shell_safety/__init__.py +6 -0
  161. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  162. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  163. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  164. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  165. code_puppy/prompts/codex_system_prompt.md +310 -0
  166. code_puppy/pydantic_patches.py +131 -0
  167. code_puppy/reopenable_async_client.py +8 -8
  168. code_puppy/round_robin_model.py +9 -12
  169. code_puppy/session_storage.py +2 -1
  170. code_puppy/status_display.py +21 -4
  171. code_puppy/summarization_agent.py +41 -13
  172. code_puppy/terminal_utils.py +418 -0
  173. code_puppy/tools/__init__.py +37 -1
  174. code_puppy/tools/agent_tools.py +536 -52
  175. code_puppy/tools/browser/__init__.py +37 -0
  176. code_puppy/tools/browser/browser_control.py +19 -23
  177. code_puppy/tools/browser/browser_interactions.py +41 -48
  178. code_puppy/tools/browser/browser_locators.py +36 -38
  179. code_puppy/tools/browser/browser_manager.py +316 -0
  180. code_puppy/tools/browser/browser_navigation.py +16 -16
  181. code_puppy/tools/browser/browser_screenshot.py +79 -143
  182. code_puppy/tools/browser/browser_scripts.py +32 -42
  183. code_puppy/tools/browser/browser_workflows.py +44 -27
  184. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  185. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  186. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  187. code_puppy/tools/browser/terminal_tools.py +525 -0
  188. code_puppy/tools/command_runner.py +930 -147
  189. code_puppy/tools/common.py +1113 -5
  190. code_puppy/tools/display.py +84 -0
  191. code_puppy/tools/file_modifications.py +288 -89
  192. code_puppy/tools/file_operations.py +226 -154
  193. code_puppy/tools/subagent_context.py +158 -0
  194. code_puppy/uvx_detection.py +242 -0
  195. code_puppy/version_checker.py +30 -11
  196. code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
  197. code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
  198. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/METADATA +149 -75
  199. code_puppy-0.0.366.dist-info/RECORD +217 -0
  200. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
  201. code_puppy/command_line/mcp/add_command.py +0 -183
  202. code_puppy/messaging/spinner/textual_spinner.py +0 -106
  203. code_puppy/tools/browser/camoufox_manager.py +0 -216
  204. code_puppy/tools/browser/vqa_agent.py +0 -70
  205. code_puppy/tui/__init__.py +0 -10
  206. code_puppy/tui/app.py +0 -1105
  207. code_puppy/tui/components/__init__.py +0 -21
  208. code_puppy/tui/components/chat_view.py +0 -551
  209. code_puppy/tui/components/command_history_modal.py +0 -218
  210. code_puppy/tui/components/copy_button.py +0 -139
  211. code_puppy/tui/components/custom_widgets.py +0 -63
  212. code_puppy/tui/components/human_input_modal.py +0 -175
  213. code_puppy/tui/components/input_area.py +0 -167
  214. code_puppy/tui/components/sidebar.py +0 -309
  215. code_puppy/tui/components/status_bar.py +0 -185
  216. code_puppy/tui/messages.py +0 -27
  217. code_puppy/tui/models/__init__.py +0 -8
  218. code_puppy/tui/models/chat_message.py +0 -25
  219. code_puppy/tui/models/command_history.py +0 -89
  220. code_puppy/tui/models/enums.py +0 -24
  221. code_puppy/tui/screens/__init__.py +0 -17
  222. code_puppy/tui/screens/autosave_picker.py +0 -175
  223. code_puppy/tui/screens/help.py +0 -130
  224. code_puppy/tui/screens/mcp_install_wizard.py +0 -803
  225. code_puppy/tui/screens/settings.py +0 -306
  226. code_puppy/tui/screens/tools.py +0 -74
  227. code_puppy/tui_state.py +0 -55
  228. code_puppy-0.0.214.data/data/code_puppy/models.json +0 -112
  229. code_puppy-0.0.214.dist-info/RECORD +0 -131
  230. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +0 -0
  231. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,329 @@
1
+ import json
2
+ from typing import Iterable
3
+
4
+ from prompt_toolkit.completion import Completer, Completion
5
+ from prompt_toolkit.document import Document
6
+
7
+
8
+ def _get_json_agents_for_model(model_name: str) -> list:
9
+ """Get JSON agents that have this model pinned in their JSON file."""
10
+ try:
11
+ from code_puppy.agents.json_agent import discover_json_agents
12
+
13
+ pinned = []
14
+ json_agents = discover_json_agents()
15
+ for agent_name, agent_path in json_agents.items():
16
+ try:
17
+ with open(agent_path, "r") as f:
18
+ agent_data = json.load(f)
19
+ if agent_data.get("model") == model_name:
20
+ pinned.append(agent_name)
21
+ except Exception:
22
+ continue
23
+ return pinned
24
+ except Exception:
25
+ return []
26
+
27
+
28
+ def _get_pinned_model_for_agent(agent_name: str) -> str | None:
29
+ """Get the pinned model for an agent (config or JSON)."""
30
+ # Check config first (for built-in agents)
31
+ try:
32
+ from code_puppy.config import get_agent_pinned_model
33
+
34
+ pinned = get_agent_pinned_model(agent_name)
35
+ if pinned:
36
+ return pinned
37
+ except Exception:
38
+ pass
39
+
40
+ # Check if it's a JSON agent with a model key
41
+ try:
42
+ from code_puppy.agents.json_agent import discover_json_agents
43
+
44
+ json_agents = discover_json_agents()
45
+ if agent_name in json_agents:
46
+ with open(json_agents[agent_name], "r") as f:
47
+ agent_data = json.load(f)
48
+ return agent_data.get("model")
49
+ except Exception:
50
+ pass
51
+
52
+ return None
53
+
54
+
55
+ def _get_model_display_meta(model_name: str) -> str:
56
+ """Get display meta for a model showing pinned agents."""
57
+ try:
58
+ from code_puppy.config import get_agents_pinned_to_model
59
+
60
+ pinned_agents = get_agents_pinned_to_model(model_name)
61
+ pinned_agents.extend(_get_json_agents_for_model(model_name))
62
+ pinned_agents = list(set(pinned_agents)) # Deduplicate
63
+
64
+ if pinned_agents:
65
+ agents_str = ", ".join(pinned_agents[:2])
66
+ if len(pinned_agents) > 2:
67
+ agents_str += "..."
68
+ return f"Pinned: [{agents_str}]"
69
+ except Exception:
70
+ pass
71
+ return "Model"
72
+
73
+
74
+ def _get_agent_display_meta(agent_name: str) -> str:
75
+ """Get display meta for an agent showing pinned model."""
76
+ pinned_model = _get_pinned_model_for_agent(agent_name)
77
+ if pinned_model:
78
+ return f"→ {pinned_model}"
79
+ return "default"
80
+
81
+
82
+ def load_agent_names():
83
+ """Load all available agent names (both built-in and JSON agents)."""
84
+ agents = set()
85
+
86
+ # Get built-in agents
87
+ try:
88
+ from code_puppy.agents.agent_manager import get_agent_descriptions
89
+
90
+ builtin_agents = get_agent_descriptions()
91
+ agents.update(builtin_agents.keys())
92
+ except Exception:
93
+ pass
94
+
95
+ # Get JSON agents
96
+ try:
97
+ from code_puppy.agents.json_agent import discover_json_agents
98
+
99
+ json_agents = discover_json_agents()
100
+ agents.update(json_agents.keys())
101
+ except Exception:
102
+ pass
103
+
104
+ return sorted(list(agents))
105
+
106
+
107
+ def load_model_names():
108
+ """Load model names from the config."""
109
+ try:
110
+ from code_puppy.command_line.model_picker_completion import (
111
+ load_model_names as load_models,
112
+ )
113
+
114
+ return load_models()
115
+ except Exception:
116
+ return []
117
+
118
+
119
+ class PinCompleter(Completer):
120
+ """
121
+ A completer that triggers on '/pin_model' to show available agents
122
+ and models for pinning a model to an agent.
123
+
124
+ Usage: /pin_model <agent-name> <model-name>
125
+ """
126
+
127
+ def __init__(self, trigger: str = "/pin_model"):
128
+ self.trigger = trigger
129
+
130
+ def get_completions(
131
+ self, document: Document, complete_event
132
+ ) -> Iterable[Completion]:
133
+ text = document.text
134
+ cursor_position = document.cursor_position
135
+ text_before_cursor = text[:cursor_position]
136
+
137
+ # Only trigger if /pin_model is at the very beginning of the line and has a space after it
138
+ stripped_text = text_before_cursor.lstrip()
139
+ if not stripped_text.startswith(self.trigger + " "):
140
+ return
141
+
142
+ # Find where /pin_model actually starts (after any leading whitespace)
143
+ trigger_pos = text_before_cursor.find(self.trigger)
144
+
145
+ # Get the command part (everything after the trigger and space)
146
+ command_part = text_before_cursor[
147
+ trigger_pos + len(self.trigger) + 1 :
148
+ ].lstrip()
149
+
150
+ # Check if we're positioned at the very end (cursor at end of text)
151
+ cursor_at_end = cursor_position == len(text)
152
+
153
+ # Better tokenization: split on spaces, but keep track of cursor position
154
+ tokens = command_part.split() if command_part.strip() else []
155
+
156
+ # Case 1: No arguments yet - complete agent names
157
+ if len(tokens) == 0:
158
+ agent_names = load_agent_names()
159
+ for agent_name in agent_names:
160
+ yield Completion(
161
+ agent_name,
162
+ start_position=-len(command_part),
163
+ display=agent_name,
164
+ display_meta=_get_agent_display_meta(agent_name),
165
+ )
166
+
167
+ # Case 2: Completing first argument (agent name)
168
+ elif len(tokens) == 1:
169
+ # Check cursor position to determine if we're still typing agent or ready for model
170
+ partial_agent = tokens[0]
171
+
172
+ # If we have exactly one token and the cursor is after it (with space),
173
+ # we should show model completions
174
+ if (
175
+ command_part.endswith(" ")
176
+ and cursor_at_end
177
+ and text_before_cursor.endswith(" ")
178
+ ):
179
+ # User has typed agent + space, show all models
180
+ model_names = load_model_names()
181
+ # Always show (unpin) option first
182
+ yield Completion(
183
+ "(unpin)",
184
+ start_position=0, # Insert at cursor position
185
+ display="(unpin)",
186
+ display_meta="Reset to default",
187
+ )
188
+ for model_name in model_names:
189
+ yield Completion(
190
+ model_name,
191
+ start_position=0, # Insert at cursor position
192
+ display=model_name,
193
+ display_meta=_get_model_display_meta(model_name),
194
+ )
195
+ else:
196
+ # Still typing agent name, show agent completions
197
+ agent_names = load_agent_names()
198
+ start_pos = -(len(partial_agent))
199
+
200
+ for agent_name in agent_names:
201
+ if agent_name.lower().startswith(partial_agent.lower()):
202
+ yield Completion(
203
+ agent_name,
204
+ start_position=start_pos,
205
+ display=agent_name,
206
+ display_meta=_get_agent_display_meta(agent_name),
207
+ )
208
+
209
+ # Case 3: Completing second argument (model name)
210
+ elif len(tokens) == 2:
211
+ # We're typing the model name
212
+ model_names = load_model_names()
213
+ partial_model = tokens[1]
214
+
215
+ # If partial model is empty (shouldn't happen with split), show all models + (unpin)
216
+ if not partial_model:
217
+ # Always show (unpin) option first
218
+ yield Completion(
219
+ "(unpin)",
220
+ start_position=0,
221
+ display="(unpin)",
222
+ display_meta="Reset to default",
223
+ )
224
+
225
+ for model_name in model_names:
226
+ yield Completion(
227
+ model_name,
228
+ start_position=0,
229
+ display=model_name,
230
+ display_meta=_get_model_display_meta(model_name),
231
+ )
232
+ else:
233
+ # Filter based on what the user has typed
234
+ start_pos = -(len(partial_model))
235
+
236
+ # Check if (unpin) matches the partial input (case-insensitive)
237
+ if "(unpin)".lower().startswith(partial_model.lower()):
238
+ yield Completion(
239
+ "(unpin)",
240
+ start_position=start_pos,
241
+ display="(unpin)",
242
+ display_meta="Reset to default",
243
+ )
244
+
245
+ # Filter models based on what the user has typed (case-insensitive)
246
+ for model_name in model_names:
247
+ if model_name.lower().startswith(partial_model.lower()):
248
+ yield Completion(
249
+ model_name,
250
+ start_position=start_pos,
251
+ display=model_name,
252
+ display_meta=_get_model_display_meta(model_name),
253
+ )
254
+
255
+ # Case 4: Handle special case when user selected (unpin)
256
+ elif len(tokens) >= 2 and tokens[1].lower() == "(unpin)".lower():
257
+ # No completion needed, the (unpin) option is complete
258
+ return
259
+
260
+ # Case 5: Have both agent and model - no completion needed
261
+ else:
262
+ return
263
+
264
+
265
+ # Alias for backwards compatibility
266
+ PinModelCompleter = PinCompleter
267
+
268
+
269
+ class UnpinCompleter(Completer):
270
+ """
271
+ A completer that triggers on '/unpin' to show available agents
272
+ for unpinning models from agents.
273
+
274
+ Usage: /unpin <agent-name>
275
+ """
276
+
277
+ def __init__(self, trigger: str = "/unpin"):
278
+ self.trigger = trigger
279
+
280
+ def get_completions(
281
+ self, document: Document, complete_event
282
+ ) -> Iterable[Completion]:
283
+ text = document.text
284
+ cursor_position = document.cursor_position
285
+ text_before_cursor = text[:cursor_position]
286
+
287
+ # Only trigger if /unpin is at the very beginning of the line and has a space after it
288
+ stripped_text = text_before_cursor.lstrip()
289
+ if not stripped_text.startswith(self.trigger + " "):
290
+ return
291
+
292
+ # Find where /unpin actually starts (after any leading whitespace)
293
+ trigger_pos = text_before_cursor.find(self.trigger)
294
+
295
+ # Get the command part (everything after the trigger and space)
296
+ command_part = text_before_cursor[
297
+ trigger_pos + len(self.trigger) + 1 :
298
+ ].lstrip()
299
+
300
+ # Only complete agent names (single argument)
301
+ tokens = command_part.split() if command_part.strip() else []
302
+
303
+ if len(tokens) == 0:
304
+ # Show all available agents
305
+ agent_names = load_agent_names()
306
+ for agent_name in agent_names:
307
+ yield Completion(
308
+ agent_name,
309
+ start_position=-len(command_part),
310
+ display=agent_name,
311
+ display_meta=_get_agent_display_meta(agent_name),
312
+ )
313
+ elif len(tokens) == 1:
314
+ # Filter agent names based on partial input
315
+ agent_names = load_agent_names()
316
+ partial_agent = tokens[0]
317
+ start_pos = -(len(partial_agent))
318
+
319
+ for agent_name in agent_names:
320
+ if agent_name.lower().startswith(partial_agent.lower()):
321
+ yield Completion(
322
+ agent_name,
323
+ start_position=start_pos,
324
+ display=agent_name,
325
+ display_meta=_get_agent_display_meta(agent_name),
326
+ )
327
+ else:
328
+ # No completion for additional arguments
329
+ return