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.

Files changed (33) hide show
  1. kodit/_version.py +3 -16
  2. kodit/app.py +11 -2
  3. kodit/application/services/auto_indexing_service.py +16 -7
  4. kodit/application/services/indexing_worker_service.py +154 -0
  5. kodit/application/services/queue_service.py +52 -0
  6. kodit/application/services/sync_scheduler.py +10 -48
  7. kodit/cli.py +407 -148
  8. kodit/cli_utils.py +74 -0
  9. kodit/config.py +33 -3
  10. kodit/domain/entities.py +48 -1
  11. kodit/domain/protocols.py +29 -2
  12. kodit/domain/value_objects.py +13 -0
  13. kodit/infrastructure/api/client/__init__.py +14 -0
  14. kodit/infrastructure/api/client/base.py +100 -0
  15. kodit/infrastructure/api/client/exceptions.py +21 -0
  16. kodit/infrastructure/api/client/generated_endpoints.py +27 -0
  17. kodit/infrastructure/api/client/index_client.py +57 -0
  18. kodit/infrastructure/api/client/search_client.py +86 -0
  19. kodit/infrastructure/api/v1/dependencies.py +13 -0
  20. kodit/infrastructure/api/v1/routers/indexes.py +9 -4
  21. kodit/infrastructure/enrichment/local_enrichment_provider.py +4 -1
  22. kodit/infrastructure/enrichment/openai_enrichment_provider.py +5 -1
  23. kodit/infrastructure/enrichment/utils.py +30 -0
  24. kodit/infrastructure/mappers/task_mapper.py +81 -0
  25. kodit/infrastructure/sqlalchemy/entities.py +35 -0
  26. kodit/infrastructure/sqlalchemy/task_repository.py +81 -0
  27. kodit/migrations/versions/9cf0e87de578_add_queue.py +47 -0
  28. kodit/utils/generate_api_paths.py +135 -0
  29. {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/METADATA +1 -1
  30. {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/RECORD +33 -19
  31. {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/WHEEL +0 -0
  32. {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/entry_points.txt +0 -0
  33. {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
- with_session,
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
- @with_session
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
- if auto_index:
186
- sources = await _handle_auto_index(app_context, sources)
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
- await _handle_sync(service, index_query_service, sources)
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 not sources:
195
- await _handle_list_indexes(index_query_service)
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
- # Index source with progress
204
- log_event("kodit.cli.index.create")
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
- # Create a new progress callback for the indexing operations
211
- indexing_progress_callback = create_multi_stage_progress_callback()
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
- await service.run_index(index, indexing_progress_callback)
214
- except EmptySourceError as e:
215
- log.exception("Empty source error", error=e)
216
- msg = f"""{e}. This could mean:
217
- The repository contains no supported file types
218
- All files are excluded by ignore patterns
219
- • The files contain no extractable code snippets
220
- Please check the repository contents and try again.
221
- """
222
- click.echo(msg)
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
- @with_session
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
- log_event("kodit.cli.search.code")
318
- service = create_code_indexing_application_service(
319
- app_context=app_context,
320
- session=session,
321
- )
322
-
323
- filters = _parse_filters(
324
- language, author, created_after, created_before, source_repo
325
- )
326
-
327
- snippets = await service.search(
328
- MultiSearchRequest(code_query=query, top_k=top_k, filters=filters)
329
- )
330
-
331
- if len(snippets) == 0:
332
- click.echo("No snippets found")
333
- return
334
-
335
- if output_format == "text":
336
- click.echo(MultiSearchResult.to_string(snippets))
337
- elif output_format == "json":
338
- click.echo(MultiSearchResult.to_jsonlines(snippets))
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
- @with_session
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
- log_event("kodit.cli.search.keyword")
374
- service = create_code_indexing_application_service(
375
- app_context=app_context,
376
- session=session,
377
- )
378
-
379
- filters = _parse_filters(
380
- language, author, created_after, created_before, source_repo
381
- )
382
-
383
- snippets = await service.search(
384
- MultiSearchRequest(keywords=keywords, top_k=top_k, filters=filters)
385
- )
386
-
387
- if len(snippets) == 0:
388
- click.echo("No snippets found")
389
- return
390
-
391
- if output_format == "text":
392
- click.echo(MultiSearchResult.to_string(snippets))
393
- elif output_format == "json":
394
- click.echo(MultiSearchResult.to_jsonlines(snippets))
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
- @with_session
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
- log_event("kodit.cli.search.text")
433
- service = create_code_indexing_application_service(
434
- app_context=app_context,
435
- session=session,
436
- )
437
-
438
- filters = _parse_filters(
439
- language, author, created_after, created_before, source_repo
440
- )
441
-
442
- snippets = await service.search(
443
- MultiSearchRequest(text_query=query, top_k=top_k, filters=filters)
444
- )
445
-
446
- if len(snippets) == 0:
447
- click.echo("No snippets found")
448
- return
449
-
450
- if output_format == "text":
451
- click.echo(MultiSearchResult.to_string(snippets))
452
- elif output_format == "json":
453
- click.echo(MultiSearchResult.to_jsonlines(snippets))
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
- @with_session
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
- filters = _parse_filters(
502
- language, author, created_after, created_before, source_repo
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
- filters=filters,
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
- @with_session
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
- log_event("kodit.cli.show.snippets")
545
- service = create_code_indexing_application_service(
546
- app_context=app_context,
547
- session=session,
548
- )
549
- snippets = await service.list_snippets(file_path=by_path, source_uri=by_source)
550
- if output_format == "text":
551
- click.echo(MultiSearchResult.to_string(snippets))
552
- elif output_format == "json":
553
- click.echo(MultiSearchResult.to_jsonlines(snippets))
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()