comet-code 0.1.0__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.
cli/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """CLI entrypoints."""
cli/commands.py ADDED
@@ -0,0 +1,266 @@
1
+ """Slash-command handling for the interactive shell.
2
+
3
+ Commands return a `CommandResult` so the shell loop knows whether to keep
4
+ running, exit, or just continue after printing feedback. Anything that
5
+ doesn't start with `/` is treated as a user task and handled by the shell.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ # Completable slash commands (used by the prompt_toolkit autocompleter in ui.py)
11
+ SLASH_COMMANDS = [
12
+ "/help",
13
+ "/mode", "/mode explain", "/mode debug", "/mode refactor", "/mode implement", "/mode plan",
14
+ "/explain", "/debug", "/refactor", "/implement", "/plan",
15
+ "/model",
16
+ "/tools", "/tools toggle", "/tools collapse", "/tools expand",
17
+ "/clear",
18
+ "/key", "/key show", "/key set", "/key clear",
19
+ "/exit", "/quit",
20
+ ]
21
+
22
+
23
+ from dataclasses import dataclass
24
+
25
+ from rich.console import Console
26
+ from rich.panel import Panel
27
+ from rich.table import Table
28
+
29
+ from cli.state import ShellState
30
+ from config import clear_key, config_file_path, get_stored_key, is_valid_key, mask_key, save_key
31
+ from core.orchestrator import Orchestrator
32
+ from llm.models import AVAILABLE_MODELS, find_model
33
+ from schemas.task import TaskMode
34
+
35
+
36
+ @dataclass
37
+ class CommandResult:
38
+ handled: bool # True if input was a recognized slash command
39
+ should_exit: bool = False
40
+
41
+
42
+ def _print_modes(console: Console, state: ShellState) -> None:
43
+ table = Table(title="modes", title_style="bold bright_cyan", border_style="cyan")
44
+ table.add_column("name", style="bright_white")
45
+ table.add_column("current", style="bright_magenta")
46
+ for m in TaskMode:
47
+ marker = "●" if m == state.mode else ""
48
+ table.add_row(m.value, marker)
49
+ console.print(table)
50
+
51
+
52
+ def _print_models(console: Console, state: ShellState) -> None:
53
+ table = Table(title="models", title_style="bold bright_cyan", border_style="cyan")
54
+ table.add_column("alias", style="bright_white")
55
+ table.add_column("slug", style="dim")
56
+ table.add_column("label", style="white")
57
+ table.add_column("current", style="bright_magenta")
58
+ for m in AVAILABLE_MODELS:
59
+ marker = "●" if m.slug == state.model.slug else ""
60
+ table.add_row(m.short, m.slug, m.label, marker)
61
+ console.print(table)
62
+
63
+
64
+ def _print_help(console: Console) -> None:
65
+ table = Table(title="commands", title_style="bold bright_cyan", border_style="cyan")
66
+ table.add_column("command", style="bright_white")
67
+ table.add_column("description", style="white")
68
+ rows = [
69
+ ("/help", "show this help"),
70
+ ("/mode", "show current mode and list available modes"),
71
+ ("/mode <name>", "switch to a mode (explain, debug, refactor, implement, plan)"),
72
+ ("/<mode>", "shortcut: e.g. /plan, /debug, /explain, /refactor, /implement"),
73
+ ("/model", "show current model and list available models"),
74
+ ("/model <name>", "switch model (by alias, slug, or label)"),
75
+ ("/tools", "show last run tool-call history"),
76
+ ("/tools <toggle|collapse|expand>", "set live tool view mode"),
77
+ ("/clear", "clear conversation history"),
78
+ ("/key", "show current API key source and masked value"),
79
+ ("/key set <key>", "save key to ~/.comet/config.json"),
80
+ ("/key clear", "remove stored key from config"),
81
+ ("/exit, /quit", "leave the shell"),
82
+ ]
83
+ for cmd, desc in rows:
84
+ table.add_row(cmd, desc)
85
+ console.print(table)
86
+
87
+
88
+ def _cmd_key(console: Console, args: list[str]) -> None:
89
+ import os
90
+ subcommand = args[0].lower() if args else "show"
91
+
92
+ if subcommand == "set":
93
+ if len(args) < 2:
94
+ console.print("[red]usage:[/red] /key set <api-key>")
95
+ return
96
+ key = args[1].strip()
97
+ if not is_valid_key(key):
98
+ console.print("[red]invalid key:[/red] OpenRouter keys must start with [bold]sk-or-[/bold]")
99
+ return
100
+ save_key(key)
101
+ console.print(
102
+ f"[bright_cyan]key saved →[/bright_cyan] [bold]{mask_key(key)}[/bold] "
103
+ f"[dim]({config_file_path()})[/dim]"
104
+ )
105
+ console.print("[dim]restart Comet (or set OPENROUTER_API_KEY) to use the new key in this session[/dim]")
106
+ return
107
+
108
+ if subcommand == "clear":
109
+ clear_key()
110
+ console.print(f"[bright_cyan]key removed[/bright_cyan] [dim]({config_file_path()})[/dim]")
111
+ return
112
+
113
+ # show (default)
114
+ env_key = os.environ.get("OPENROUTER_API_KEY", "").strip()
115
+ stored_key = get_stored_key()
116
+ if env_key:
117
+ console.print(
118
+ f"[bright_cyan]key source →[/bright_cyan] [bold]env var[/bold] (OPENROUTER_API_KEY) "
119
+ f"[dim]{mask_key(env_key)}[/dim]"
120
+ )
121
+ elif stored_key:
122
+ console.print(
123
+ f"[bright_cyan]key source →[/bright_cyan] [bold]config file[/bold] "
124
+ f"[dim]{mask_key(stored_key)} ({config_file_path()})[/dim]"
125
+ )
126
+ else:
127
+ console.print(
128
+ "[yellow]no API key configured[/yellow] "
129
+ "[dim]run /key set <key> or set OPENROUTER_API_KEY[/dim]"
130
+ )
131
+
132
+
133
+ def _cmd_mode(console: Console, state: ShellState, args: list[str]) -> None:
134
+ if not args:
135
+ _print_modes(console, state)
136
+ return
137
+ name = args[0].lower()
138
+ try:
139
+ state.mode = TaskMode(name)
140
+ except ValueError:
141
+ console.print(
142
+ f"[red]unknown mode:[/red] {name} "
143
+ f"[dim](try: {', '.join(TaskMode.names())})[/dim]"
144
+ )
145
+ return
146
+ console.print(f"[bright_cyan]mode →[/bright_cyan] [bold]{state.mode.value}[/bold]")
147
+
148
+
149
+ def _cmd_model(console: Console, state: ShellState, args: list[str]) -> None:
150
+ if not args:
151
+ _print_models(console, state)
152
+ return
153
+ query = " ".join(args)
154
+ found = find_model(query)
155
+ if found is None:
156
+ console.print(f"[red]unknown model:[/red] {query} [dim](try /model for the list)[/dim]")
157
+ return
158
+ state.model = found
159
+ console.print(
160
+ f"[bright_cyan]model →[/bright_cyan] [bold]{found.label}[/bold] "
161
+ f"[dim]({found.slug})[/dim]"
162
+ )
163
+
164
+
165
+ def _print_tool_history(console: Console, state: ShellState) -> None:
166
+ mode_label = "collapsed" if state.tool_view_collapsed else "expanded"
167
+ if not state.last_tool_history:
168
+ console.print(
169
+ f"[bright_cyan]tools view →[/bright_cyan] [bold]{mode_label}[/bold] "
170
+ f"[dim](no tool history yet)[/dim]"
171
+ )
172
+ return
173
+
174
+ lines: list[str] = []
175
+ for idx, item in enumerate(state.last_tool_history, start=1):
176
+ status = item.get("status", "unknown")
177
+ tool_name = item.get("tool_name", "tool")
178
+ args_str = item.get("args_json", "{}")
179
+ lines.append(f"{idx}. {tool_name} {args_str} [{status}]")
180
+ reason = item.get("reason")
181
+ if reason:
182
+ lines.append(f" why: {reason}")
183
+ preview = item.get("preview")
184
+ if preview:
185
+ lines.append(f" {preview}")
186
+ error = item.get("error")
187
+ if error:
188
+ lines.append(f" error: {error}")
189
+
190
+ console.print(
191
+ Panel(
192
+ "\n".join(lines),
193
+ title=f"[bold bright_cyan]tool history ({mode_label})[/bold bright_cyan]",
194
+ border_style="cyan",
195
+ padding=(1, 2),
196
+ expand=True,
197
+ )
198
+ )
199
+
200
+
201
+ def handle_command(
202
+ text: str,
203
+ console: Console,
204
+ state: ShellState,
205
+ orchestrator: Orchestrator,
206
+ ) -> CommandResult:
207
+ """Dispatch a slash-prefixed line. Non-slash lines return handled=False."""
208
+ if not text.startswith("/"):
209
+ return CommandResult(handled=False)
210
+
211
+ parts = text[1:].split()
212
+ if not parts:
213
+ return CommandResult(handled=True)
214
+
215
+ name, args = parts[0].lower(), parts[1:]
216
+
217
+ if name in {"exit", "quit"}:
218
+ return CommandResult(handled=True, should_exit=True)
219
+
220
+ if name == "help":
221
+ _print_help(console)
222
+ return CommandResult(handled=True)
223
+
224
+ if name == "mode":
225
+ _cmd_mode(console, state, args)
226
+ return CommandResult(handled=True)
227
+
228
+ if name == "model":
229
+ _cmd_model(console, state, args)
230
+ return CommandResult(handled=True)
231
+
232
+ if name == "clear":
233
+ orchestrator.reset_history()
234
+ state.last_tool_history = []
235
+ console.print("[bright_cyan]history cleared[/bright_cyan]")
236
+ return CommandResult(handled=True)
237
+
238
+ if name == "key":
239
+ _cmd_key(console, args)
240
+ return CommandResult(handled=True)
241
+
242
+ if name == "tools":
243
+ if args:
244
+ action = args[0].lower()
245
+ if action == "toggle":
246
+ state.tool_view_collapsed = not state.tool_view_collapsed
247
+ elif action == "collapse":
248
+ state.tool_view_collapsed = True
249
+ elif action == "expand":
250
+ state.tool_view_collapsed = False
251
+ else:
252
+ console.print("[red]unknown /tools option:[/red] " + action)
253
+ return CommandResult(handled=True)
254
+
255
+ _print_tool_history(console, state)
256
+ return CommandResult(handled=True)
257
+
258
+ # Mode shortcuts: /plan, /debug, /explain, /refactor, /implement
259
+ if name in TaskMode.names():
260
+ _cmd_mode(console, state, [name])
261
+ return CommandResult(handled=True)
262
+
263
+ console.print(
264
+ f"[red]unknown command:[/red] /{name} [dim](try /help)[/dim]"
265
+ )
266
+ return CommandResult(handled=True)
cli/completer.py ADDED
@@ -0,0 +1,18 @@
1
+ """Prompt Toolkit completer for Comet slash commands.
2
+
3
+ Provides a simple WordCompleter that suggests all commands defined in
4
+ `cli.commands.SLASH_COMMANDS`. The completer is used by the interactive UI
5
+ so that pressing <Tab> (or any configured completion key) shows available
6
+ commands.
7
+ """
8
+
9
+ from prompt_toolkit.completion import WordCompleter
10
+
11
+ from .commands import SLASH_COMMANDS
12
+
13
+ # Case‑insensitive, matches whole words (including the leading '/')
14
+ command_completer = WordCompleter(
15
+ SLASH_COMMANDS,
16
+ ignore_case=True,
17
+ sentence=True,
18
+ )