kodit 0.3.16__py3-none-any.whl → 0.3.17__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.
Potentially problematic release.
This version of kodit might be problematic. Click here for more details.
- kodit/_version.py +3 -16
- kodit/app.py +11 -2
- kodit/application/services/auto_indexing_service.py +16 -7
- kodit/application/services/indexing_worker_service.py +154 -0
- kodit/application/services/queue_service.py +52 -0
- kodit/application/services/sync_scheduler.py +10 -48
- kodit/cli.py +407 -148
- kodit/cli_utils.py +74 -0
- kodit/config.py +33 -3
- kodit/domain/entities.py +48 -1
- kodit/domain/protocols.py +29 -2
- kodit/domain/value_objects.py +13 -0
- kodit/infrastructure/api/client/__init__.py +14 -0
- kodit/infrastructure/api/client/base.py +100 -0
- kodit/infrastructure/api/client/exceptions.py +21 -0
- kodit/infrastructure/api/client/generated_endpoints.py +27 -0
- kodit/infrastructure/api/client/index_client.py +57 -0
- kodit/infrastructure/api/client/search_client.py +86 -0
- kodit/infrastructure/api/v1/dependencies.py +13 -0
- kodit/infrastructure/api/v1/routers/indexes.py +9 -4
- kodit/infrastructure/enrichment/local_enrichment_provider.py +4 -1
- kodit/infrastructure/enrichment/openai_enrichment_provider.py +5 -1
- kodit/infrastructure/enrichment/utils.py +30 -0
- kodit/infrastructure/mappers/task_mapper.py +81 -0
- kodit/infrastructure/sqlalchemy/entities.py +35 -0
- kodit/infrastructure/sqlalchemy/task_repository.py +81 -0
- kodit/migrations/versions/9cf0e87de578_add_queue.py +47 -0
- kodit/utils/generate_api_paths.py +135 -0
- {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/METADATA +1 -1
- {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/RECORD +33 -19
- {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/WHEEL +0 -0
- {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/entry_points.txt +0 -0
- {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/licenses/LICENSE +0 -0
kodit/cli.py
CHANGED
|
@@ -9,7 +9,6 @@ import click
|
|
|
9
9
|
import structlog
|
|
10
10
|
import uvicorn
|
|
11
11
|
from pytable_formatter import Cell, Table # type: ignore[import-untyped]
|
|
12
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
13
12
|
|
|
14
13
|
from kodit.application.factories.code_indexing_factory import (
|
|
15
14
|
create_code_indexing_application_service,
|
|
@@ -17,7 +16,7 @@ from kodit.application.factories.code_indexing_factory import (
|
|
|
17
16
|
from kodit.config import (
|
|
18
17
|
AppContext,
|
|
19
18
|
with_app_context,
|
|
20
|
-
|
|
19
|
+
wrap_async,
|
|
21
20
|
)
|
|
22
21
|
from kodit.domain.errors import EmptySourceError
|
|
23
22
|
from kodit.domain.services.index_query_service import IndexQueryService
|
|
@@ -26,6 +25,7 @@ from kodit.domain.value_objects import (
|
|
|
26
25
|
MultiSearchResult,
|
|
27
26
|
SnippetSearchFilters,
|
|
28
27
|
)
|
|
28
|
+
from kodit.infrastructure.api.client import IndexClient, SearchClient
|
|
29
29
|
from kodit.infrastructure.indexing.fusion_service import ReciprocalRankFusionService
|
|
30
30
|
from kodit.infrastructure.sqlalchemy.index_repository import SqlAlchemyIndexRepository
|
|
31
31
|
from kodit.infrastructure.ui.progress import (
|
|
@@ -162,9 +162,8 @@ async def _handle_list_indexes(index_query_service: IndexQueryService) -> None:
|
|
|
162
162
|
)
|
|
163
163
|
@click.option("--sync", is_flag=True, help="Sync existing indexes with their remotes")
|
|
164
164
|
@with_app_context
|
|
165
|
-
@
|
|
165
|
+
@wrap_async
|
|
166
166
|
async def index(
|
|
167
|
-
session: AsyncSession,
|
|
168
167
|
app_context: AppContext,
|
|
169
168
|
sources: list[str],
|
|
170
169
|
*, # Force keyword-only arguments
|
|
@@ -172,54 +171,294 @@ async def index(
|
|
|
172
171
|
sync: bool,
|
|
173
172
|
) -> None:
|
|
174
173
|
"""List indexes, index data sources, or sync existing indexes."""
|
|
174
|
+
if not app_context.is_remote:
|
|
175
|
+
# Local mode - use existing implementation
|
|
176
|
+
await _index_local(app_context, sources, auto_index, sync)
|
|
177
|
+
else:
|
|
178
|
+
# Remote mode - use API clients
|
|
179
|
+
await _index_remote(app_context, sources, auto_index, sync)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def _index_local(
|
|
183
|
+
app_context: AppContext,
|
|
184
|
+
sources: list[str],
|
|
185
|
+
auto_index: bool, # noqa: FBT001
|
|
186
|
+
sync: bool, # noqa: FBT001
|
|
187
|
+
) -> None:
|
|
188
|
+
"""Handle index operations in local mode."""
|
|
175
189
|
log = structlog.get_logger(__name__)
|
|
176
|
-
service = create_code_indexing_application_service(
|
|
177
|
-
app_context=app_context,
|
|
178
|
-
session=session,
|
|
179
|
-
)
|
|
180
|
-
index_query_service = IndexQueryService(
|
|
181
|
-
index_repository=SqlAlchemyIndexRepository(session=session),
|
|
182
|
-
fusion_service=ReciprocalRankFusionService(),
|
|
183
|
-
)
|
|
184
190
|
|
|
185
|
-
|
|
186
|
-
|
|
191
|
+
# Get database session
|
|
192
|
+
db = await app_context.get_db()
|
|
193
|
+
async with db.session_factory() as session:
|
|
194
|
+
service = create_code_indexing_application_service(
|
|
195
|
+
app_context=app_context,
|
|
196
|
+
session=session,
|
|
197
|
+
)
|
|
198
|
+
index_query_service = IndexQueryService(
|
|
199
|
+
index_repository=SqlAlchemyIndexRepository(session=session),
|
|
200
|
+
fusion_service=ReciprocalRankFusionService(),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if auto_index:
|
|
204
|
+
sources = await _handle_auto_index(app_context, sources)
|
|
205
|
+
if not sources:
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
if sync:
|
|
209
|
+
await _handle_sync(service, index_query_service, sources)
|
|
210
|
+
return
|
|
211
|
+
|
|
187
212
|
if not sources:
|
|
213
|
+
await _handle_list_indexes(index_query_service)
|
|
188
214
|
return
|
|
189
215
|
|
|
216
|
+
# Handle source indexing
|
|
217
|
+
for source in sources:
|
|
218
|
+
if Path(source).is_file():
|
|
219
|
+
msg = "File indexing is not implemented yet"
|
|
220
|
+
raise click.UsageError(msg)
|
|
221
|
+
|
|
222
|
+
# Index source with progress
|
|
223
|
+
log_event("kodit.cli.index.create")
|
|
224
|
+
|
|
225
|
+
# Create a lazy progress callback that only shows progress when needed
|
|
226
|
+
progress_callback = create_lazy_progress_callback()
|
|
227
|
+
index = await service.create_index_from_uri(source, progress_callback)
|
|
228
|
+
|
|
229
|
+
# Create a new progress callback for the indexing operations
|
|
230
|
+
indexing_progress_callback = create_multi_stage_progress_callback()
|
|
231
|
+
try:
|
|
232
|
+
await service.run_index(index, indexing_progress_callback)
|
|
233
|
+
except EmptySourceError as e:
|
|
234
|
+
log.exception("Empty source error", error=e)
|
|
235
|
+
msg = f"""{e}. This could mean:
|
|
236
|
+
• The repository contains no supported file types
|
|
237
|
+
• All files are excluded by ignore patterns
|
|
238
|
+
• The files contain no extractable code snippets
|
|
239
|
+
Please check the repository contents and try again.
|
|
240
|
+
"""
|
|
241
|
+
click.echo(msg)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
async def _index_remote(
|
|
245
|
+
app_context: AppContext,
|
|
246
|
+
sources: list[str],
|
|
247
|
+
auto_index: bool, # noqa: FBT001
|
|
248
|
+
sync: bool, # noqa: FBT001
|
|
249
|
+
) -> None:
|
|
250
|
+
"""Handle index operations in remote mode."""
|
|
190
251
|
if sync:
|
|
191
|
-
|
|
252
|
+
# Sync operation not available in remote mode
|
|
253
|
+
click.echo("⚠️ Warning: Index sync is not implemented in remote mode")
|
|
254
|
+
click.echo("Please use the server's auto-sync functionality or sync locally")
|
|
192
255
|
return
|
|
193
256
|
|
|
194
|
-
if
|
|
195
|
-
|
|
257
|
+
if auto_index:
|
|
258
|
+
click.echo("⚠️ Warning: Auto-index is not implemented in remote mode")
|
|
259
|
+
click.echo("Please configure sources to be auto-indexed on the server")
|
|
196
260
|
return
|
|
197
|
-
# Handle source indexing
|
|
198
|
-
for source in sources:
|
|
199
|
-
if Path(source).is_file():
|
|
200
|
-
msg = "File indexing is not implemented yet"
|
|
201
|
-
raise click.UsageError(msg)
|
|
202
261
|
|
|
203
|
-
|
|
204
|
-
|
|
262
|
+
# Create API client
|
|
263
|
+
index_client = IndexClient(
|
|
264
|
+
base_url=app_context.remote.server_url or "",
|
|
265
|
+
api_key=app_context.remote.api_key,
|
|
266
|
+
timeout=app_context.remote.timeout,
|
|
267
|
+
max_retries=app_context.remote.max_retries,
|
|
268
|
+
verify_ssl=app_context.remote.verify_ssl,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
if not sources:
|
|
273
|
+
# List indexes
|
|
274
|
+
indexes = await index_client.list_indexes()
|
|
275
|
+
_display_indexes_remote(indexes)
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
# Create new indexes
|
|
279
|
+
for source in sources:
|
|
280
|
+
click.echo(f"Creating index for: {source}")
|
|
281
|
+
index = await index_client.create_index(source)
|
|
282
|
+
click.echo(f"✓ Index created with ID: {index.id}")
|
|
283
|
+
|
|
284
|
+
finally:
|
|
285
|
+
await index_client.close()
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _display_indexes_remote(indexes: list[Any]) -> None:
|
|
289
|
+
"""Display indexes for remote mode."""
|
|
290
|
+
headers = [
|
|
291
|
+
"ID",
|
|
292
|
+
"Created At",
|
|
293
|
+
"Updated At",
|
|
294
|
+
"Source",
|
|
295
|
+
]
|
|
296
|
+
data = [
|
|
297
|
+
[
|
|
298
|
+
index.id,
|
|
299
|
+
index.attributes.created_at,
|
|
300
|
+
index.attributes.updated_at,
|
|
301
|
+
index.attributes.uri,
|
|
302
|
+
]
|
|
303
|
+
for index in indexes
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
click.echo(Table(headers=headers, data=data))
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
async def _search_local( # noqa: PLR0913
|
|
310
|
+
app_context: AppContext,
|
|
311
|
+
keywords: list[str] | None = None,
|
|
312
|
+
code_query: str | None = None,
|
|
313
|
+
text_query: str | None = None,
|
|
314
|
+
top_k: int = 10,
|
|
315
|
+
language: str | None = None,
|
|
316
|
+
author: str | None = None,
|
|
317
|
+
created_after: str | None = None,
|
|
318
|
+
created_before: str | None = None,
|
|
319
|
+
source_repo: str | None = None,
|
|
320
|
+
output_format: str = "text",
|
|
321
|
+
event_name: str = "kodit.cli.search",
|
|
322
|
+
) -> None:
|
|
323
|
+
"""Handle search operations in local mode."""
|
|
324
|
+
log_event(event_name)
|
|
325
|
+
|
|
326
|
+
# Get database session
|
|
327
|
+
db = await app_context.get_db()
|
|
328
|
+
async with db.session_factory() as session:
|
|
329
|
+
service = create_code_indexing_application_service(
|
|
330
|
+
app_context=app_context,
|
|
331
|
+
session=session,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
filters = _parse_filters(
|
|
335
|
+
language, author, created_after, created_before, source_repo
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
snippets = await service.search(
|
|
339
|
+
MultiSearchRequest(
|
|
340
|
+
keywords=keywords,
|
|
341
|
+
code_query=code_query,
|
|
342
|
+
text_query=text_query,
|
|
343
|
+
top_k=top_k,
|
|
344
|
+
filters=filters,
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
if len(snippets) == 0:
|
|
349
|
+
click.echo("No snippets found")
|
|
350
|
+
return
|
|
351
|
+
|
|
352
|
+
if output_format == "text":
|
|
353
|
+
click.echo(MultiSearchResult.to_string(snippets))
|
|
354
|
+
elif output_format == "json":
|
|
355
|
+
click.echo(MultiSearchResult.to_jsonlines(snippets))
|
|
205
356
|
|
|
206
|
-
# Create a lazy progress callback that only shows progress when needed
|
|
207
|
-
progress_callback = create_lazy_progress_callback()
|
|
208
|
-
index = await service.create_index_from_uri(source, progress_callback)
|
|
209
357
|
|
|
210
|
-
|
|
211
|
-
|
|
358
|
+
async def _search_remote( # noqa: PLR0913
|
|
359
|
+
app_context: AppContext,
|
|
360
|
+
keywords: list[str] | None = None,
|
|
361
|
+
code_query: str | None = None,
|
|
362
|
+
text_query: str | None = None,
|
|
363
|
+
top_k: int = 10,
|
|
364
|
+
language: str | None = None,
|
|
365
|
+
author: str | None = None,
|
|
366
|
+
created_after: str | None = None,
|
|
367
|
+
created_before: str | None = None,
|
|
368
|
+
source_repo: str | None = None,
|
|
369
|
+
output_format: str = "text",
|
|
370
|
+
) -> None:
|
|
371
|
+
"""Handle search operations in remote mode."""
|
|
372
|
+
from datetime import datetime
|
|
373
|
+
|
|
374
|
+
# Parse date filters for API
|
|
375
|
+
parsed_start_date = None
|
|
376
|
+
if created_after:
|
|
212
377
|
try:
|
|
213
|
-
|
|
214
|
-
except
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
378
|
+
parsed_start_date = datetime.fromisoformat(created_after)
|
|
379
|
+
except ValueError as err:
|
|
380
|
+
raise ValueError(
|
|
381
|
+
f"Invalid date format for --created-after: {created_after}. "
|
|
382
|
+
"Expected ISO 8601 format (YYYY-MM-DD)"
|
|
383
|
+
) from err
|
|
384
|
+
|
|
385
|
+
parsed_end_date = None
|
|
386
|
+
if created_before:
|
|
387
|
+
try:
|
|
388
|
+
parsed_end_date = datetime.fromisoformat(created_before)
|
|
389
|
+
except ValueError as err:
|
|
390
|
+
raise ValueError(
|
|
391
|
+
f"Invalid date format for --created-before: {created_before}. "
|
|
392
|
+
"Expected ISO 8601 format (YYYY-MM-DD)"
|
|
393
|
+
) from err
|
|
394
|
+
|
|
395
|
+
# Create API client
|
|
396
|
+
search_client = SearchClient(
|
|
397
|
+
base_url=app_context.remote.server_url or "",
|
|
398
|
+
api_key=app_context.remote.api_key,
|
|
399
|
+
timeout=app_context.remote.timeout,
|
|
400
|
+
max_retries=app_context.remote.max_retries,
|
|
401
|
+
verify_ssl=app_context.remote.verify_ssl,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
try:
|
|
405
|
+
# Perform search
|
|
406
|
+
snippets = await search_client.search(
|
|
407
|
+
keywords=keywords,
|
|
408
|
+
code_query=code_query,
|
|
409
|
+
text_query=text_query,
|
|
410
|
+
limit=top_k,
|
|
411
|
+
languages=[language] if language else None,
|
|
412
|
+
authors=[author] if author else None,
|
|
413
|
+
start_date=parsed_start_date,
|
|
414
|
+
end_date=parsed_end_date,
|
|
415
|
+
sources=[source_repo] if source_repo else None,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
if len(snippets) == 0:
|
|
419
|
+
click.echo("No snippets found")
|
|
420
|
+
return
|
|
421
|
+
|
|
422
|
+
if output_format == "text":
|
|
423
|
+
_display_snippets_remote_text(snippets)
|
|
424
|
+
elif output_format == "json":
|
|
425
|
+
_display_snippets_remote_json(snippets)
|
|
426
|
+
|
|
427
|
+
finally:
|
|
428
|
+
await search_client.close()
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _display_snippets_remote_text(snippets: list[Any]) -> None:
|
|
432
|
+
"""Display snippets in text format for remote mode."""
|
|
433
|
+
for i, snippet in enumerate(snippets):
|
|
434
|
+
click.echo(f"\n--- Snippet {i + 1} ---")
|
|
435
|
+
click.echo(f"ID: {snippet.id}")
|
|
436
|
+
click.echo(f"Language: {snippet.attributes.language}")
|
|
437
|
+
click.echo(f"Source: {snippet.attributes.source_uri}")
|
|
438
|
+
click.echo(f"Path: {snippet.attributes.relative_path}")
|
|
439
|
+
click.echo(f"Authors: {', '.join(snippet.attributes.authors)}")
|
|
440
|
+
click.echo(f"Summary: {snippet.attributes.summary}")
|
|
441
|
+
click.echo("\nContent:")
|
|
442
|
+
click.echo(snippet.attributes.content)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _display_snippets_remote_json(snippets: list[Any]) -> None:
|
|
446
|
+
"""Display snippets in JSON format for remote mode."""
|
|
447
|
+
import json
|
|
448
|
+
|
|
449
|
+
for snippet in snippets:
|
|
450
|
+
snippet_data = {
|
|
451
|
+
"id": snippet.id,
|
|
452
|
+
"language": snippet.attributes.language,
|
|
453
|
+
"source_uri": snippet.attributes.source_uri,
|
|
454
|
+
"relative_path": snippet.attributes.relative_path,
|
|
455
|
+
"authors": snippet.attributes.authors,
|
|
456
|
+
"summary": snippet.attributes.summary,
|
|
457
|
+
"content": snippet.attributes.content,
|
|
458
|
+
"created_at": snippet.attributes.created_at.isoformat(),
|
|
459
|
+
"updated_at": snippet.attributes.updated_at.isoformat(),
|
|
460
|
+
}
|
|
461
|
+
click.echo(json.dumps(snippet_data))
|
|
223
462
|
|
|
224
463
|
|
|
225
464
|
@cli.group()
|
|
@@ -297,9 +536,8 @@ def _parse_filters(
|
|
|
297
536
|
)
|
|
298
537
|
@click.option("--output-format", default="text", help="Format to display snippets in")
|
|
299
538
|
@with_app_context
|
|
300
|
-
@
|
|
539
|
+
@wrap_async
|
|
301
540
|
async def code( # noqa: PLR0913
|
|
302
|
-
session: AsyncSession,
|
|
303
541
|
app_context: AppContext,
|
|
304
542
|
query: str,
|
|
305
543
|
top_k: int,
|
|
@@ -314,28 +552,31 @@ async def code( # noqa: PLR0913
|
|
|
314
552
|
|
|
315
553
|
This works best if your query is code.
|
|
316
554
|
"""
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
555
|
+
if not app_context.is_remote:
|
|
556
|
+
await _search_local(
|
|
557
|
+
app_context,
|
|
558
|
+
code_query=query,
|
|
559
|
+
top_k=top_k,
|
|
560
|
+
language=language,
|
|
561
|
+
author=author,
|
|
562
|
+
created_after=created_after,
|
|
563
|
+
created_before=created_before,
|
|
564
|
+
source_repo=source_repo,
|
|
565
|
+
output_format=output_format,
|
|
566
|
+
event_name="kodit.cli.search.code",
|
|
567
|
+
)
|
|
568
|
+
else:
|
|
569
|
+
await _search_remote(
|
|
570
|
+
app_context,
|
|
571
|
+
code_query=query,
|
|
572
|
+
top_k=top_k,
|
|
573
|
+
language=language,
|
|
574
|
+
author=author,
|
|
575
|
+
created_after=created_after,
|
|
576
|
+
created_before=created_before,
|
|
577
|
+
source_repo=source_repo,
|
|
578
|
+
output_format=output_format,
|
|
579
|
+
)
|
|
339
580
|
|
|
340
581
|
|
|
341
582
|
@search.command()
|
|
@@ -356,9 +597,8 @@ async def code( # noqa: PLR0913
|
|
|
356
597
|
)
|
|
357
598
|
@click.option("--output-format", default="text", help="Format to display snippets in")
|
|
358
599
|
@with_app_context
|
|
359
|
-
@
|
|
600
|
+
@wrap_async
|
|
360
601
|
async def keyword( # noqa: PLR0913
|
|
361
|
-
session: AsyncSession,
|
|
362
602
|
app_context: AppContext,
|
|
363
603
|
keywords: list[str],
|
|
364
604
|
top_k: int,
|
|
@@ -370,28 +610,32 @@ async def keyword( # noqa: PLR0913
|
|
|
370
610
|
output_format: str,
|
|
371
611
|
) -> None:
|
|
372
612
|
"""Search for snippets using keyword search."""
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
613
|
+
# Override remote configuration if provided via CLI
|
|
614
|
+
if not app_context.is_remote:
|
|
615
|
+
await _search_local(
|
|
616
|
+
app_context,
|
|
617
|
+
keywords=keywords,
|
|
618
|
+
top_k=top_k,
|
|
619
|
+
language=language,
|
|
620
|
+
author=author,
|
|
621
|
+
created_after=created_after,
|
|
622
|
+
created_before=created_before,
|
|
623
|
+
source_repo=source_repo,
|
|
624
|
+
output_format=output_format,
|
|
625
|
+
event_name="kodit.cli.search.keyword",
|
|
626
|
+
)
|
|
627
|
+
else:
|
|
628
|
+
await _search_remote(
|
|
629
|
+
app_context,
|
|
630
|
+
keywords=keywords,
|
|
631
|
+
top_k=top_k,
|
|
632
|
+
language=language,
|
|
633
|
+
author=author,
|
|
634
|
+
created_after=created_after,
|
|
635
|
+
created_before=created_before,
|
|
636
|
+
source_repo=source_repo,
|
|
637
|
+
output_format=output_format,
|
|
638
|
+
)
|
|
395
639
|
|
|
396
640
|
|
|
397
641
|
@search.command()
|
|
@@ -412,9 +656,8 @@ async def keyword( # noqa: PLR0913
|
|
|
412
656
|
)
|
|
413
657
|
@click.option("--output-format", default="text", help="Format to display snippets in")
|
|
414
658
|
@with_app_context
|
|
415
|
-
@
|
|
659
|
+
@wrap_async
|
|
416
660
|
async def text( # noqa: PLR0913
|
|
417
|
-
session: AsyncSession,
|
|
418
661
|
app_context: AppContext,
|
|
419
662
|
query: str,
|
|
420
663
|
top_k: int,
|
|
@@ -429,28 +672,31 @@ async def text( # noqa: PLR0913
|
|
|
429
672
|
|
|
430
673
|
This works best if your query is text.
|
|
431
674
|
"""
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
675
|
+
if not app_context.is_remote:
|
|
676
|
+
await _search_local(
|
|
677
|
+
app_context,
|
|
678
|
+
text_query=query,
|
|
679
|
+
top_k=top_k,
|
|
680
|
+
language=language,
|
|
681
|
+
author=author,
|
|
682
|
+
created_after=created_after,
|
|
683
|
+
created_before=created_before,
|
|
684
|
+
source_repo=source_repo,
|
|
685
|
+
output_format=output_format,
|
|
686
|
+
event_name="kodit.cli.search.text",
|
|
687
|
+
)
|
|
688
|
+
else:
|
|
689
|
+
await _search_remote(
|
|
690
|
+
app_context,
|
|
691
|
+
text_query=query,
|
|
692
|
+
top_k=top_k,
|
|
693
|
+
language=language,
|
|
694
|
+
author=author,
|
|
695
|
+
created_after=created_after,
|
|
696
|
+
created_before=created_before,
|
|
697
|
+
source_repo=source_repo,
|
|
698
|
+
output_format=output_format,
|
|
699
|
+
)
|
|
454
700
|
|
|
455
701
|
|
|
456
702
|
@search.command()
|
|
@@ -473,9 +719,8 @@ async def text( # noqa: PLR0913
|
|
|
473
719
|
)
|
|
474
720
|
@click.option("--output-format", default="text", help="Format to display snippets in")
|
|
475
721
|
@with_app_context
|
|
476
|
-
@
|
|
722
|
+
@wrap_async
|
|
477
723
|
async def hybrid( # noqa: PLR0913
|
|
478
|
-
session: AsyncSession,
|
|
479
724
|
app_context: AppContext,
|
|
480
725
|
top_k: int,
|
|
481
726
|
keywords: str,
|
|
@@ -489,37 +734,38 @@ async def hybrid( # noqa: PLR0913
|
|
|
489
734
|
output_format: str,
|
|
490
735
|
) -> None:
|
|
491
736
|
"""Search for snippets using hybrid search."""
|
|
492
|
-
log_event("kodit.cli.search.hybrid")
|
|
493
|
-
service = create_code_indexing_application_service(
|
|
494
|
-
app_context=app_context,
|
|
495
|
-
session=session,
|
|
496
|
-
)
|
|
497
|
-
|
|
498
737
|
# Parse keywords into a list of strings
|
|
499
738
|
keywords_list = [k.strip().lower() for k in keywords.split(",")]
|
|
500
739
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
snippets = await service.search(
|
|
506
|
-
MultiSearchRequest(
|
|
740
|
+
if not app_context.is_remote:
|
|
741
|
+
await _search_local(
|
|
742
|
+
app_context,
|
|
507
743
|
keywords=keywords_list,
|
|
508
744
|
code_query=code,
|
|
509
745
|
text_query=text,
|
|
510
746
|
top_k=top_k,
|
|
511
|
-
|
|
747
|
+
language=language,
|
|
748
|
+
author=author,
|
|
749
|
+
created_after=created_after,
|
|
750
|
+
created_before=created_before,
|
|
751
|
+
source_repo=source_repo,
|
|
752
|
+
output_format=output_format,
|
|
753
|
+
event_name="kodit.cli.search.hybrid",
|
|
754
|
+
)
|
|
755
|
+
else:
|
|
756
|
+
await _search_remote(
|
|
757
|
+
app_context,
|
|
758
|
+
keywords=keywords_list,
|
|
759
|
+
code_query=code,
|
|
760
|
+
text_query=text,
|
|
761
|
+
top_k=top_k,
|
|
762
|
+
language=language,
|
|
763
|
+
author=author,
|
|
764
|
+
created_after=created_after,
|
|
765
|
+
created_before=created_before,
|
|
766
|
+
source_repo=source_repo,
|
|
767
|
+
output_format=output_format,
|
|
512
768
|
)
|
|
513
|
-
)
|
|
514
|
-
|
|
515
|
-
if len(snippets) == 0:
|
|
516
|
-
click.echo("No snippets found")
|
|
517
|
-
return
|
|
518
|
-
|
|
519
|
-
if output_format == "text":
|
|
520
|
-
click.echo(MultiSearchResult.to_string(snippets))
|
|
521
|
-
elif output_format == "json":
|
|
522
|
-
click.echo(MultiSearchResult.to_jsonlines(snippets))
|
|
523
769
|
|
|
524
770
|
|
|
525
771
|
@cli.group()
|
|
@@ -532,25 +778,38 @@ def show() -> None:
|
|
|
532
778
|
@click.option("--by-source", help="Source URI to filter snippets by")
|
|
533
779
|
@click.option("--output-format", default="text", help="Format to display snippets in")
|
|
534
780
|
@with_app_context
|
|
535
|
-
@
|
|
781
|
+
@wrap_async
|
|
536
782
|
async def snippets(
|
|
537
|
-
session: AsyncSession,
|
|
538
783
|
app_context: AppContext,
|
|
539
784
|
by_path: str | None,
|
|
540
785
|
by_source: str | None,
|
|
541
786
|
output_format: str,
|
|
542
787
|
) -> None:
|
|
543
788
|
"""Show snippets with optional filtering by path or source."""
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
789
|
+
if not app_context.is_remote:
|
|
790
|
+
# Local mode
|
|
791
|
+
log_event("kodit.cli.show.snippets")
|
|
792
|
+
db = await app_context.get_db()
|
|
793
|
+
async with db.session_factory() as session:
|
|
794
|
+
service = create_code_indexing_application_service(
|
|
795
|
+
app_context=app_context,
|
|
796
|
+
session=session,
|
|
797
|
+
)
|
|
798
|
+
snippets = await service.list_snippets(
|
|
799
|
+
file_path=by_path, source_uri=by_source
|
|
800
|
+
)
|
|
801
|
+
if output_format == "text":
|
|
802
|
+
click.echo(MultiSearchResult.to_string(snippets))
|
|
803
|
+
elif output_format == "json":
|
|
804
|
+
click.echo(MultiSearchResult.to_jsonlines(snippets))
|
|
805
|
+
else:
|
|
806
|
+
# Remote mode - not supported
|
|
807
|
+
click.echo("⚠️ Warning: 'show snippets' is not implemented in remote mode")
|
|
808
|
+
click.echo(
|
|
809
|
+
"This functionality is only available when connected directly "
|
|
810
|
+
"to the database"
|
|
811
|
+
)
|
|
812
|
+
click.echo("Use 'kodit search' commands instead for remote snippet retrieval")
|
|
554
813
|
|
|
555
814
|
|
|
556
815
|
@cli.command()
|