cicada-mcp 0.1.5__py3-none-any.whl → 0.2.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.
- cicada/ascii_art.py +60 -0
- cicada/clean.py +195 -60
- cicada/cli.py +757 -0
- cicada/colors.py +27 -0
- cicada/command_logger.py +14 -16
- cicada/dead_code_analyzer.py +12 -19
- cicada/extractors/__init__.py +6 -6
- cicada/extractors/base.py +3 -3
- cicada/extractors/call.py +11 -15
- cicada/extractors/dependency.py +39 -51
- cicada/extractors/doc.py +8 -9
- cicada/extractors/function.py +12 -24
- cicada/extractors/module.py +11 -15
- cicada/extractors/spec.py +8 -12
- cicada/find_dead_code.py +15 -39
- cicada/formatter.py +37 -91
- cicada/git_helper.py +22 -34
- cicada/indexer.py +165 -132
- cicada/interactive_setup.py +490 -0
- cicada/keybert_extractor.py +286 -0
- cicada/keyword_search.py +22 -30
- cicada/keyword_test.py +127 -0
- cicada/lightweight_keyword_extractor.py +5 -13
- cicada/mcp_entry.py +683 -0
- cicada/mcp_server.py +110 -232
- cicada/parser.py +9 -9
- cicada/pr_finder.py +15 -19
- cicada/pr_indexer/__init__.py +3 -3
- cicada/pr_indexer/cli.py +4 -9
- cicada/pr_indexer/github_api_client.py +22 -37
- cicada/pr_indexer/indexer.py +17 -29
- cicada/pr_indexer/line_mapper.py +8 -12
- cicada/pr_indexer/pr_index_builder.py +22 -34
- cicada/setup.py +198 -89
- cicada/utils/__init__.py +9 -9
- cicada/utils/call_site_formatter.py +4 -6
- cicada/utils/function_grouper.py +4 -4
- cicada/utils/hash_utils.py +12 -15
- cicada/utils/index_utils.py +15 -15
- cicada/utils/path_utils.py +24 -29
- cicada/utils/signature_builder.py +3 -3
- cicada/utils/subprocess_runner.py +17 -19
- cicada/utils/text_utils.py +1 -2
- cicada/version_check.py +2 -5
- {cicada_mcp-0.1.5.dist-info → cicada_mcp-0.2.0.dist-info}/METADATA +144 -55
- cicada_mcp-0.2.0.dist-info/RECORD +53 -0
- cicada_mcp-0.2.0.dist-info/entry_points.txt +4 -0
- cicada/install.py +0 -741
- cicada_mcp-0.1.5.dist-info/RECORD +0 -47
- cicada_mcp-0.1.5.dist-info/entry_points.txt +0 -9
- {cicada_mcp-0.1.5.dist-info → cicada_mcp-0.2.0.dist-info}/WHEEL +0 -0
- {cicada_mcp-0.1.5.dist-info → cicada_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {cicada_mcp-0.1.5.dist-info → cicada_mcp-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
"""Interactive first-time setup menu for cicada."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import cast
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from simple_term_menu import TerminalMenu
|
|
9
|
+
|
|
10
|
+
has_terminal_menu = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
TerminalMenu = None # type: ignore
|
|
13
|
+
has_terminal_menu = False
|
|
14
|
+
|
|
15
|
+
from cicada.ascii_art import generate_gradient_ascii_art
|
|
16
|
+
from cicada.colors import BOLD, GREEN, GREY, PRIMARY, RESET, SELECTED
|
|
17
|
+
from cicada.setup import EditorType
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _text_based_setup() -> tuple[str, str]:
|
|
21
|
+
"""
|
|
22
|
+
Fallback text-based setup for terminals that don't support simple-term-menu.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
tuple[str, str]: The selected extraction method and model tier
|
|
26
|
+
"""
|
|
27
|
+
print(f"{PRIMARY}{'=' * 70}{RESET}")
|
|
28
|
+
print(f"{SELECTED}🦗 Welcome to CICADA - Elixir Code Intelligence{RESET}")
|
|
29
|
+
print(f"{PRIMARY}{'=' * 70}{RESET}")
|
|
30
|
+
print()
|
|
31
|
+
print(f"This is your first time running CICADA in this project.{RESET}")
|
|
32
|
+
print(f"Let's configure keyword extraction for code intelligence.{RESET}")
|
|
33
|
+
print()
|
|
34
|
+
print(f"{BOLD}Step 1/2: Choose extraction method{RESET}")
|
|
35
|
+
print()
|
|
36
|
+
print("1. Lemminflect - Grammar-based keyword extraction (fast, proven)")
|
|
37
|
+
print("2. KeyBERT - Semantic keyword extraction (AI embeddings)")
|
|
38
|
+
print()
|
|
39
|
+
|
|
40
|
+
while True:
|
|
41
|
+
try:
|
|
42
|
+
method_choice = input("Enter your choice (1 or 2) [default: 1]: ").strip()
|
|
43
|
+
if not method_choice:
|
|
44
|
+
method_choice = "1"
|
|
45
|
+
if method_choice in ("1", "2"):
|
|
46
|
+
method = "lemminflect" if method_choice == "1" else "bert"
|
|
47
|
+
break
|
|
48
|
+
print("Invalid choice. Please enter 1 or 2.")
|
|
49
|
+
except (KeyboardInterrupt, EOFError):
|
|
50
|
+
print()
|
|
51
|
+
print("Setup cancelled. Exiting...")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
# For lemminflect, no tier selection - it's always the same
|
|
55
|
+
print()
|
|
56
|
+
if method == "lemminflect":
|
|
57
|
+
print(f"{BOLD} What is Lemminflect?{RESET}")
|
|
58
|
+
print(f" Lemminflect finds keywords using grammar rules + word importance{RESET}")
|
|
59
|
+
print()
|
|
60
|
+
print(f"{GREEN}✓{RESET} Selected: LEMMINFLECT")
|
|
61
|
+
print()
|
|
62
|
+
return ("lemminflect", "regular")
|
|
63
|
+
|
|
64
|
+
# For KeyBERT, ask for tier
|
|
65
|
+
print(f"{SELECTED} What is KeyBERT?{RESET}")
|
|
66
|
+
print(f"{PRIMARY} KeyBERT uses AI embeddings to find semantically similar keywords{RESET}")
|
|
67
|
+
print()
|
|
68
|
+
print("1. Fast (80MB, ~1s) - Recommended for bigger projects")
|
|
69
|
+
print("2. Regular (133MB, ~1.4s) - Better semantic understanding [recommended]")
|
|
70
|
+
print("3. Max (420MB, ~6.5s) - Highest quality embeddings")
|
|
71
|
+
|
|
72
|
+
print()
|
|
73
|
+
print(f"{BOLD}Step 2/2: Choose model tier{RESET}")
|
|
74
|
+
print()
|
|
75
|
+
|
|
76
|
+
while True:
|
|
77
|
+
try:
|
|
78
|
+
tier_choice = input("Enter your choice (1, 2, or 3) [default: 2]: ").strip()
|
|
79
|
+
if not tier_choice:
|
|
80
|
+
tier_choice = "2"
|
|
81
|
+
if tier_choice in ("1", "2", "3"):
|
|
82
|
+
tier_map = {"1": "fast", "2": "regular", "3": "max"}
|
|
83
|
+
tier = tier_map[tier_choice]
|
|
84
|
+
break
|
|
85
|
+
print("Invalid choice. Please enter 1, 2, or 3.")
|
|
86
|
+
except (KeyboardInterrupt, EOFError):
|
|
87
|
+
print()
|
|
88
|
+
print(f"{SELECTED}Setup cancelled. Exiting...{RESET}")
|
|
89
|
+
sys.exit(1)
|
|
90
|
+
|
|
91
|
+
print()
|
|
92
|
+
print(f"{GREEN}✓{RESET} Selected: KeyBERT - {tier.capitalize()} model")
|
|
93
|
+
print()
|
|
94
|
+
|
|
95
|
+
return ("bert", tier)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def show_first_time_setup() -> tuple[str, str]:
|
|
99
|
+
"""
|
|
100
|
+
Display an interactive first-time setup menu for cicada.
|
|
101
|
+
|
|
102
|
+
Falls back to text-based input if the terminal doesn't support simple-term-menu.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
tuple[str, str]: The selected extraction method and model tier
|
|
106
|
+
e.g., ('lemminflect', 'regular') or ('bert', 'fast')
|
|
107
|
+
"""
|
|
108
|
+
# Check if terminal menu is available and supported
|
|
109
|
+
if not has_terminal_menu:
|
|
110
|
+
return _text_based_setup()
|
|
111
|
+
|
|
112
|
+
# Display ASCII art
|
|
113
|
+
print(generate_gradient_ascii_art())
|
|
114
|
+
|
|
115
|
+
# Step 1: Choose extraction method
|
|
116
|
+
print(f"{PRIMARY}{'=' * 70}{RESET}")
|
|
117
|
+
print(f"{SELECTED}🦗 Welcome to CICADA - Elixir Code Intelligence{RESET}")
|
|
118
|
+
print(f"{PRIMARY}{'=' * 70}{RESET}")
|
|
119
|
+
print()
|
|
120
|
+
print(f"This is your first time running CICADA in this project.{RESET}")
|
|
121
|
+
print(f"Let's configure keyword extraction for code intelligence.{RESET}")
|
|
122
|
+
print()
|
|
123
|
+
print(f"{BOLD}Step 1/2: Choose extraction method{RESET}")
|
|
124
|
+
|
|
125
|
+
method_items = [
|
|
126
|
+
"Lemminflect - Grammar-based keyword extraction (fast, proven)",
|
|
127
|
+
"KeyBERT - Semantic keyword extraction (AI embeddings)",
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
if TerminalMenu is None:
|
|
132
|
+
return _text_based_setup()
|
|
133
|
+
method_menu = TerminalMenu(
|
|
134
|
+
method_items,
|
|
135
|
+
title="",
|
|
136
|
+
menu_cursor="» ",
|
|
137
|
+
menu_cursor_style=("fg_yellow", "bold"),
|
|
138
|
+
menu_highlight_style=("fg_yellow", "bold"),
|
|
139
|
+
cycle_cursor=True,
|
|
140
|
+
clear_screen=False,
|
|
141
|
+
)
|
|
142
|
+
method_index = method_menu.show()
|
|
143
|
+
except (KeyboardInterrupt, EOFError):
|
|
144
|
+
print()
|
|
145
|
+
print("Setup cancelled. Exiting...")
|
|
146
|
+
sys.exit(1)
|
|
147
|
+
except Exception:
|
|
148
|
+
# Terminal doesn't support the menu - fall back to text-based
|
|
149
|
+
print(
|
|
150
|
+
f"\n{GREY}Note: Terminal menu not supported, using text-based input{RESET}\n",
|
|
151
|
+
file=sys.stderr,
|
|
152
|
+
)
|
|
153
|
+
return _text_based_setup()
|
|
154
|
+
|
|
155
|
+
if method_index is None:
|
|
156
|
+
print()
|
|
157
|
+
print("Setup cancelled. Exiting...")
|
|
158
|
+
sys.exit(1)
|
|
159
|
+
|
|
160
|
+
method = "lemminflect" if method_index == 0 else "bert"
|
|
161
|
+
|
|
162
|
+
# For lemminflect, no tier selection - it's always the same
|
|
163
|
+
print()
|
|
164
|
+
if method == "lemminflect":
|
|
165
|
+
print(f"{BOLD} What is Lemminflect?{RESET}")
|
|
166
|
+
print(f" Lemminflect finds keywords using grammar rules + word importance{RESET}")
|
|
167
|
+
print(f' Example: "We use Kubernetes for container orchestration"{RESET}')
|
|
168
|
+
print(f' Output: "Kubernetes", "container", "orchestration"{RESET}')
|
|
169
|
+
print()
|
|
170
|
+
print(f"{GREEN}✓{RESET} Selected: LEMMINFLECT")
|
|
171
|
+
print()
|
|
172
|
+
return ("lemminflect", "regular")
|
|
173
|
+
|
|
174
|
+
# For KeyBERT, ask for tier
|
|
175
|
+
print(f"{SELECTED} What is KeyBERT?{RESET}")
|
|
176
|
+
print(f"{PRIMARY} KeyBERT uses AI embeddings to find semantically similar keywords{RESET}")
|
|
177
|
+
print(f'{PRIMARY} Example: "We use Kubernetes for container orchestration"{RESET}')
|
|
178
|
+
print(f'{PRIMARY} Output: "Kubernetes", "deployment", "microservices", "DevOps"{RESET}')
|
|
179
|
+
print()
|
|
180
|
+
tier_items = [
|
|
181
|
+
"Fast (80MB, ~1s) - Recommended for bigger projects",
|
|
182
|
+
"Regular [recommended] (133MB, ~1.4s) - Better semantic understanding",
|
|
183
|
+
"Max (420MB, ~6.5s) - Highest quality embeddings",
|
|
184
|
+
]
|
|
185
|
+
print(f"{SELECTED}Step 2/2: Choose model tier\n")
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
if TerminalMenu is None:
|
|
189
|
+
return _text_based_setup()
|
|
190
|
+
tier_menu = TerminalMenu(
|
|
191
|
+
tier_items,
|
|
192
|
+
title="",
|
|
193
|
+
menu_cursor="» ",
|
|
194
|
+
menu_cursor_style=("fg_yellow", "bold"),
|
|
195
|
+
menu_highlight_style=("fg_yellow", "bold"),
|
|
196
|
+
cycle_cursor=True,
|
|
197
|
+
clear_screen=False,
|
|
198
|
+
)
|
|
199
|
+
tier_index = tier_menu.show()
|
|
200
|
+
except (KeyboardInterrupt, EOFError):
|
|
201
|
+
print()
|
|
202
|
+
print(f"{SELECTED}Setup cancelled. Exiting...{RESET}")
|
|
203
|
+
sys.exit(1)
|
|
204
|
+
except Exception:
|
|
205
|
+
# Terminal doesn't support the menu - fall back to text-based
|
|
206
|
+
print(
|
|
207
|
+
f"\n{GREY}Note: Terminal menu not supported, using text-based input{RESET}\n",
|
|
208
|
+
file=sys.stderr,
|
|
209
|
+
)
|
|
210
|
+
# Recreate the selection for model tier based on already selected method
|
|
211
|
+
return _text_based_setup()
|
|
212
|
+
|
|
213
|
+
if tier_index is None:
|
|
214
|
+
print()
|
|
215
|
+
print(f"{SELECTED}Setup cancelled. Exiting...{RESET}")
|
|
216
|
+
sys.exit(1)
|
|
217
|
+
|
|
218
|
+
tier_map = {0: "fast", 1: "regular", 2: "max"}
|
|
219
|
+
# Ensure tier_index is treated as int (TerminalMenu.show() returns int | tuple | None)
|
|
220
|
+
tier = tier_map[int(tier_index) if isinstance(tier_index, int) else tier_index[0]]
|
|
221
|
+
|
|
222
|
+
print()
|
|
223
|
+
print(f"{GREEN}✓{RESET} Selected: KeyBERT - {tier.capitalize()} model")
|
|
224
|
+
print()
|
|
225
|
+
|
|
226
|
+
return ("bert", tier)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def show_full_interactive_setup(repo_path: str | Path | None = None) -> None:
|
|
230
|
+
"""
|
|
231
|
+
Display full interactive setup including editor selection and keyword extraction.
|
|
232
|
+
|
|
233
|
+
This is the main entry point when running `cicada` with no arguments or a path.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
repo_path: Path to the Elixir repository. Defaults to current directory.
|
|
237
|
+
"""
|
|
238
|
+
from cicada.setup import setup
|
|
239
|
+
|
|
240
|
+
# Check if we're in an Elixir project
|
|
241
|
+
repo_path = Path.cwd() if repo_path is None else Path(repo_path).resolve()
|
|
242
|
+
if not (repo_path / "mix.exs").exists():
|
|
243
|
+
print(f"{PRIMARY}Error: {repo_path} does not appear to be an Elixir project{RESET}")
|
|
244
|
+
print(f"{GREY}(mix.exs not found){RESET}")
|
|
245
|
+
print()
|
|
246
|
+
print("Please run cicada from the root of an Elixir project.")
|
|
247
|
+
sys.exit(1)
|
|
248
|
+
|
|
249
|
+
# Display ASCII art
|
|
250
|
+
print(generate_gradient_ascii_art())
|
|
251
|
+
|
|
252
|
+
# Step 1: Choose editor
|
|
253
|
+
print(f"{PRIMARY}{'=' * 70}{RESET}")
|
|
254
|
+
print(f"{SELECTED}🦗 Welcome to CICADA - Elixir Code Intelligence{RESET}")
|
|
255
|
+
print(f"{PRIMARY}{'=' * 70}{RESET}")
|
|
256
|
+
print()
|
|
257
|
+
print(f"Let's set up Cicada for your editor and project.{RESET}")
|
|
258
|
+
print()
|
|
259
|
+
print(f"{BOLD}Step 1/3: Choose your editor{RESET}")
|
|
260
|
+
|
|
261
|
+
editor_items = [
|
|
262
|
+
"Claude Code - AI-powered code editor",
|
|
263
|
+
"Cursor - AI-first code editor",
|
|
264
|
+
"VS Code - Visual Studio Code",
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
if has_terminal_menu:
|
|
268
|
+
try:
|
|
269
|
+
if TerminalMenu is None:
|
|
270
|
+
# Fallback to text-based
|
|
271
|
+
editor = _text_based_editor_selection()
|
|
272
|
+
else:
|
|
273
|
+
editor_menu = TerminalMenu(
|
|
274
|
+
editor_items,
|
|
275
|
+
title="",
|
|
276
|
+
menu_cursor="» ",
|
|
277
|
+
menu_cursor_style=("fg_yellow", "bold"),
|
|
278
|
+
menu_highlight_style=("fg_yellow", "bold"),
|
|
279
|
+
cycle_cursor=True,
|
|
280
|
+
clear_screen=False,
|
|
281
|
+
)
|
|
282
|
+
editor_index = editor_menu.show()
|
|
283
|
+
|
|
284
|
+
if editor_index is None:
|
|
285
|
+
print()
|
|
286
|
+
print("Setup cancelled. Exiting...")
|
|
287
|
+
sys.exit(1)
|
|
288
|
+
|
|
289
|
+
editor_map = {0: "claude", 1: "cursor", 2: "vs"}
|
|
290
|
+
editor = editor_map[
|
|
291
|
+
int(editor_index) if isinstance(editor_index, int) else editor_index[0]
|
|
292
|
+
]
|
|
293
|
+
except (KeyboardInterrupt, EOFError):
|
|
294
|
+
print()
|
|
295
|
+
print("Setup cancelled. Exiting...")
|
|
296
|
+
sys.exit(1)
|
|
297
|
+
except Exception:
|
|
298
|
+
# Terminal doesn't support the menu - fall back to text-based
|
|
299
|
+
print(
|
|
300
|
+
f"\n{GREY}Note: Terminal menu not supported, using text-based input{RESET}\n",
|
|
301
|
+
file=sys.stderr,
|
|
302
|
+
)
|
|
303
|
+
editor = _text_based_editor_selection()
|
|
304
|
+
else:
|
|
305
|
+
editor = _text_based_editor_selection()
|
|
306
|
+
|
|
307
|
+
print()
|
|
308
|
+
print(f"{GREEN}✓{RESET} Selected: {editor.upper()}")
|
|
309
|
+
print()
|
|
310
|
+
|
|
311
|
+
# Check if index already exists before showing model selection
|
|
312
|
+
from cicada.utils.storage import get_config_path, get_index_path
|
|
313
|
+
|
|
314
|
+
config_path = get_config_path(repo_path)
|
|
315
|
+
index_path = get_index_path(repo_path)
|
|
316
|
+
|
|
317
|
+
if config_path.exists() and index_path.exists():
|
|
318
|
+
# Index exists - use existing settings, don't show model selection
|
|
319
|
+
import yaml
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
with open(config_path) as f:
|
|
323
|
+
existing_config = yaml.safe_load(f)
|
|
324
|
+
method = existing_config.get("keyword_extraction", {}).get("method", "lemminflect")
|
|
325
|
+
tier = existing_config.get("keyword_extraction", {}).get("tier", "regular")
|
|
326
|
+
|
|
327
|
+
# Run setup with existing settings
|
|
328
|
+
try:
|
|
329
|
+
setup(
|
|
330
|
+
cast(EditorType, editor),
|
|
331
|
+
repo_path,
|
|
332
|
+
keyword_method=method,
|
|
333
|
+
keyword_tier=tier,
|
|
334
|
+
index_exists=True,
|
|
335
|
+
)
|
|
336
|
+
except Exception as e:
|
|
337
|
+
print(f"\n{PRIMARY}Error: Setup failed: {e}{RESET}")
|
|
338
|
+
sys.exit(1)
|
|
339
|
+
|
|
340
|
+
return # Exit early - don't show model selection
|
|
341
|
+
except Exception:
|
|
342
|
+
# If we can't read config, proceed with model selection
|
|
343
|
+
pass
|
|
344
|
+
|
|
345
|
+
# Step 2: Choose keyword extraction method
|
|
346
|
+
print(f"{BOLD}Step 2/3: Choose extraction method{RESET}")
|
|
347
|
+
|
|
348
|
+
method_items = [
|
|
349
|
+
"Lemminflect - Grammar-based keyword extraction (fast, proven)",
|
|
350
|
+
"KeyBERT - Semantic keyword extraction (AI embeddings)",
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
if has_terminal_menu:
|
|
354
|
+
try:
|
|
355
|
+
if TerminalMenu is None:
|
|
356
|
+
method, tier = show_first_time_setup()
|
|
357
|
+
return
|
|
358
|
+
method_menu = TerminalMenu(
|
|
359
|
+
method_items,
|
|
360
|
+
title="",
|
|
361
|
+
menu_cursor="» ",
|
|
362
|
+
menu_cursor_style=("fg_yellow", "bold"),
|
|
363
|
+
menu_highlight_style=("fg_yellow", "bold"),
|
|
364
|
+
cycle_cursor=True,
|
|
365
|
+
clear_screen=False,
|
|
366
|
+
)
|
|
367
|
+
method_index = method_menu.show()
|
|
368
|
+
|
|
369
|
+
if method_index is None:
|
|
370
|
+
print()
|
|
371
|
+
print("Setup cancelled. Exiting...")
|
|
372
|
+
sys.exit(1)
|
|
373
|
+
|
|
374
|
+
method = "lemminflect" if method_index == 0 else "bert"
|
|
375
|
+
except (KeyboardInterrupt, EOFError):
|
|
376
|
+
print()
|
|
377
|
+
print("Setup cancelled. Exiting...")
|
|
378
|
+
sys.exit(1)
|
|
379
|
+
except Exception:
|
|
380
|
+
print(
|
|
381
|
+
f"\n{GREY}Note: Terminal menu not supported, using text-based input{RESET}\n",
|
|
382
|
+
file=sys.stderr,
|
|
383
|
+
)
|
|
384
|
+
method, tier = show_first_time_setup()
|
|
385
|
+
return
|
|
386
|
+
else:
|
|
387
|
+
method, tier = show_first_time_setup()
|
|
388
|
+
return
|
|
389
|
+
|
|
390
|
+
# For lemminflect, no tier selection needed - always uses default
|
|
391
|
+
if method == "lemminflect":
|
|
392
|
+
print()
|
|
393
|
+
print(f"{BOLD} What is Lemminflect?{RESET}")
|
|
394
|
+
print(f" Lemminflect finds keywords using grammar rules + word importance{RESET}")
|
|
395
|
+
print()
|
|
396
|
+
print(f"{GREEN}✓{RESET} Selected: LEMMINFLECT")
|
|
397
|
+
print()
|
|
398
|
+
tier = "regular" # Default tier (not used for lemminflect, but needed for API)
|
|
399
|
+
else:
|
|
400
|
+
# Step 3: Choose model tier (only for BERT)
|
|
401
|
+
print()
|
|
402
|
+
print(f"{SELECTED} What is KeyBERT?{RESET}")
|
|
403
|
+
print(
|
|
404
|
+
f"{PRIMARY} KeyBERT uses AI embeddings to find semantically similar keywords{RESET}"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
tier_items = [
|
|
408
|
+
"Fast (80MB, ~1s) - Recommended for bigger projects",
|
|
409
|
+
"Regular [recommended] (133MB, ~1.4s) - Better semantic understanding",
|
|
410
|
+
"Max (420MB, ~6.5s) - Highest quality embeddings",
|
|
411
|
+
]
|
|
412
|
+
|
|
413
|
+
print()
|
|
414
|
+
print(f"{BOLD}Step 3/3: Choose model tier{RESET}")
|
|
415
|
+
print()
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
if TerminalMenu is None:
|
|
419
|
+
method, tier = show_first_time_setup()
|
|
420
|
+
return
|
|
421
|
+
tier_menu = TerminalMenu(
|
|
422
|
+
tier_items,
|
|
423
|
+
title="",
|
|
424
|
+
menu_cursor="» ",
|
|
425
|
+
menu_cursor_style=("fg_yellow", "bold"),
|
|
426
|
+
menu_highlight_style=("fg_yellow", "bold"),
|
|
427
|
+
cycle_cursor=True,
|
|
428
|
+
clear_screen=False,
|
|
429
|
+
)
|
|
430
|
+
tier_index = tier_menu.show()
|
|
431
|
+
except (KeyboardInterrupt, EOFError):
|
|
432
|
+
print()
|
|
433
|
+
print(f"{SELECTED}Setup cancelled. Exiting...{RESET}")
|
|
434
|
+
sys.exit(1)
|
|
435
|
+
except Exception:
|
|
436
|
+
print(
|
|
437
|
+
f"\n{GREY}Note: Terminal menu not supported, using text-based input{RESET}\n",
|
|
438
|
+
file=sys.stderr,
|
|
439
|
+
)
|
|
440
|
+
method, tier = show_first_time_setup()
|
|
441
|
+
return
|
|
442
|
+
|
|
443
|
+
if tier_index is None:
|
|
444
|
+
print()
|
|
445
|
+
print(f"{SELECTED}Setup cancelled. Exiting...{RESET}")
|
|
446
|
+
sys.exit(1)
|
|
447
|
+
|
|
448
|
+
tier_map = {0: "fast", 1: "regular", 2: "max"}
|
|
449
|
+
tier = tier_map[int(tier_index) if isinstance(tier_index, int) else tier_index[0]]
|
|
450
|
+
|
|
451
|
+
print()
|
|
452
|
+
print(f"{GREEN}✓{RESET} Selected: KeyBERT - {tier.capitalize()} model")
|
|
453
|
+
print()
|
|
454
|
+
|
|
455
|
+
# Run setup
|
|
456
|
+
print(f"{BOLD}Running setup...{RESET}")
|
|
457
|
+
print()
|
|
458
|
+
|
|
459
|
+
try:
|
|
460
|
+
setup(cast(EditorType, editor), repo_path, keyword_method=method, keyword_tier=tier)
|
|
461
|
+
except Exception as e:
|
|
462
|
+
print(f"\n{PRIMARY}Error: Setup failed: {e}{RESET}")
|
|
463
|
+
sys.exit(1)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _text_based_editor_selection() -> str:
|
|
467
|
+
"""
|
|
468
|
+
Fallback text-based editor selection for terminals that don't support simple-term-menu.
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
str: The selected editor ('claude', 'cursor', or 'vs')
|
|
472
|
+
"""
|
|
473
|
+
print("1. Claude Code - AI-powered code editor")
|
|
474
|
+
print("2. Cursor - AI-first code editor")
|
|
475
|
+
print("3. VS Code - Visual Studio Code")
|
|
476
|
+
print()
|
|
477
|
+
|
|
478
|
+
while True:
|
|
479
|
+
try:
|
|
480
|
+
choice = input("Enter your choice (1, 2, or 3) [default: 1]: ").strip()
|
|
481
|
+
if not choice:
|
|
482
|
+
choice = "1"
|
|
483
|
+
if choice in ("1", "2", "3"):
|
|
484
|
+
editor_map = {"1": "claude", "2": "cursor", "3": "vs"}
|
|
485
|
+
return editor_map[choice]
|
|
486
|
+
print("Invalid choice. Please enter 1, 2, or 3.")
|
|
487
|
+
except (KeyboardInterrupt, EOFError):
|
|
488
|
+
print()
|
|
489
|
+
print("Setup cancelled. Exiting...")
|
|
490
|
+
sys.exit(1)
|