aline-ai 0.5.3__py3-none-any.whl → 0.5.5__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 (80) hide show
  1. {aline_ai-0.5.3.dist-info → aline_ai-0.5.5.dist-info}/METADATA +1 -1
  2. aline_ai-0.5.5.dist-info/RECORD +93 -0
  3. realign/__init__.py +1 -1
  4. realign/adapters/antigravity.py +28 -20
  5. realign/adapters/base.py +46 -50
  6. realign/adapters/claude.py +14 -14
  7. realign/adapters/codex.py +7 -7
  8. realign/adapters/gemini.py +11 -11
  9. realign/adapters/registry.py +14 -10
  10. realign/claude_detector.py +2 -2
  11. realign/claude_hooks/__init__.py +3 -3
  12. realign/claude_hooks/permission_request_hook.py +35 -0
  13. realign/claude_hooks/permission_request_hook_installer.py +31 -32
  14. realign/claude_hooks/stop_hook.py +4 -1
  15. realign/claude_hooks/stop_hook_installer.py +30 -31
  16. realign/cli.py +24 -0
  17. realign/codex_detector.py +11 -11
  18. realign/commands/add.py +361 -35
  19. realign/commands/config.py +3 -12
  20. realign/commands/context.py +3 -1
  21. realign/commands/export_shares.py +86 -127
  22. realign/commands/import_shares.py +145 -155
  23. realign/commands/init.py +166 -30
  24. realign/commands/restore.py +18 -6
  25. realign/commands/search.py +14 -42
  26. realign/commands/upgrade.py +155 -11
  27. realign/commands/watcher.py +98 -219
  28. realign/commands/worker.py +29 -6
  29. realign/config.py +25 -20
  30. realign/context.py +1 -3
  31. realign/dashboard/app.py +4 -4
  32. realign/dashboard/screens/create_event.py +3 -1
  33. realign/dashboard/screens/event_detail.py +14 -6
  34. realign/dashboard/screens/session_detail.py +3 -1
  35. realign/dashboard/screens/share_import.py +7 -3
  36. realign/dashboard/tmux_manager.py +91 -22
  37. realign/dashboard/widgets/config_panel.py +85 -1
  38. realign/dashboard/widgets/events_table.py +3 -1
  39. realign/dashboard/widgets/header.py +1 -0
  40. realign/dashboard/widgets/search_panel.py +37 -27
  41. realign/dashboard/widgets/sessions_table.py +24 -15
  42. realign/dashboard/widgets/terminal_panel.py +207 -17
  43. realign/dashboard/widgets/watcher_panel.py +6 -2
  44. realign/dashboard/widgets/worker_panel.py +10 -1
  45. realign/db/__init__.py +1 -1
  46. realign/db/base.py +5 -15
  47. realign/db/locks.py +0 -1
  48. realign/db/migration.py +82 -76
  49. realign/db/schema.py +2 -6
  50. realign/db/sqlite_db.py +23 -41
  51. realign/events/__init__.py +0 -1
  52. realign/events/event_summarizer.py +27 -15
  53. realign/events/session_summarizer.py +29 -15
  54. realign/file_lock.py +1 -0
  55. realign/hooks.py +150 -60
  56. realign/logging_config.py +12 -15
  57. realign/mcp_server.py +30 -51
  58. realign/mcp_watcher.py +0 -1
  59. realign/models/event.py +29 -20
  60. realign/prompts/__init__.py +7 -7
  61. realign/prompts/presets.py +15 -11
  62. realign/redactor.py +99 -59
  63. realign/triggers/__init__.py +9 -9
  64. realign/triggers/antigravity_trigger.py +30 -28
  65. realign/triggers/base.py +4 -3
  66. realign/triggers/claude_trigger.py +104 -85
  67. realign/triggers/codex_trigger.py +15 -5
  68. realign/triggers/gemini_trigger.py +57 -47
  69. realign/triggers/next_turn_trigger.py +3 -1
  70. realign/triggers/registry.py +6 -2
  71. realign/triggers/turn_status.py +3 -1
  72. realign/watcher_core.py +306 -131
  73. realign/watcher_daemon.py +8 -8
  74. realign/worker_core.py +3 -1
  75. realign/worker_daemon.py +3 -1
  76. aline_ai-0.5.3.dist-info/RECORD +0 -93
  77. {aline_ai-0.5.3.dist-info → aline_ai-0.5.5.dist-info}/WHEEL +0 -0
  78. {aline_ai-0.5.3.dist-info → aline_ai-0.5.5.dist-info}/entry_points.txt +0 -0
  79. {aline_ai-0.5.3.dist-info → aline_ai-0.5.5.dist-info}/licenses/LICENSE +0 -0
  80. {aline_ai-0.5.3.dist-info → aline_ai-0.5.5.dist-info}/top_level.txt +0 -0
realign/commands/add.py CHANGED
@@ -251,10 +251,233 @@ Use this skill when the user wants to:
251
251
  - Send Slack updates about completed work
252
252
  """
253
253
 
254
+ # Aline Import History Sessions skill definition for Claude Code
255
+ # Installed to ~/.claude/skills/aline-import-history-sessions/SKILL.md
256
+ ALINE_IMPORT_HISTORY_SESSIONS_SKILL_MD = """---
257
+ name: aline-import-history-sessions
258
+ description: Guide users through importing Claude Code session history into Aline database. Use this for first-time setup, onboarding new users, or when users want to selectively import historical sessions. Provides interactive workflow with progress checking.
259
+ ---
260
+
261
+ # Aline Import History Sessions Skill
262
+
263
+ This skill guides users through the process of importing Claude Code session history into Aline's database. It provides an interactive, step-by-step workflow to help users discover, select, and import their historical sessions.
264
+
265
+ ## Workflow Overview
266
+
267
+ ```
268
+ Analyze Unimported Sessions → Present Summary → User Selection → Import Sessions → Verify Success → Continue or Finish
269
+ ```
270
+
271
+ ## Step-by-Step Guide
272
+
273
+ ### Step 1: Analyze Current Status
274
+
275
+ First, list all sessions to understand what hasn't been imported yet:
276
+
277
+ ```bash
278
+ aline watcher session list --detect-turns
279
+ ```
280
+
281
+ **Internal analysis (do NOT expose status terminology to user):**
282
+ - Count sessions with status `new` → these are "unimported sessions"
283
+ - Count sessions with status `partial` → these have "updates available"
284
+ - Count sessions with status `tracked` → these are "already imported"
285
+
286
+ Parse the output to extract:
287
+ - Total number of unimported sessions
288
+ - Their session IDs (use these for import, NOT index numbers)
289
+ - Project paths they belong to
290
+
291
+ ### Step 2: Present Summary to User
292
+
293
+ Present a user-friendly summary WITHOUT mentioning internal status labels:
294
+
295
+ Example:
296
+ > "I found **47 sessions** in your Claude Code history:
297
+ > - **12 sessions** haven't been imported yet
298
+ > - **3 sessions** have updates since last import
299
+ > - **32 sessions** are already fully imported
300
+ >
301
+ > The unimported sessions span these projects:
302
+ > - `/Users/you/Projects/ProjectA` (5 sessions)
303
+ > - `/Users/you/Projects/ProjectB` (7 sessions)"
304
+
305
+ ### Step 3: Ask User Import Preferences
306
+
307
+ Use `AskUserQuestion` to understand what the user wants to import:
308
+
309
+ ```json
310
+ {
311
+ "questions": [{
312
+ "header": "Import scope",
313
+ "question": "Which sessions would you like to import?",
314
+ "options": [
315
+ {"label": "All unimported (Recommended)", "description": "Import all 12 sessions that haven't been imported yet"},
316
+ {"label": "Include updates", "description": "Import unimported sessions + update 3 sessions with new content"},
317
+ {"label": "Select by project", "description": "Choose which project's sessions to import"},
318
+ {"label": "Select specific", "description": "I'll review and pick individual sessions"}
319
+ ],
320
+ "multiSelect": false
321
+ }]
322
+ }
323
+ ```
324
+
325
+ ### Step 4: Handle User Selection
326
+
327
+ #### If "All unimported":
328
+ Confirm the import with session count:
329
+ ```json
330
+ {
331
+ "questions": [{
332
+ "header": "Confirm",
333
+ "question": "Ready to import 12 sessions. Proceed?",
334
+ "options": [
335
+ {"label": "Yes, import", "description": "Start importing all unimported sessions"},
336
+ {"label": "Let me review first", "description": "Show me the session list to review"}
337
+ ],
338
+ "multiSelect": false
339
+ }]
340
+ }
341
+ ```
342
+
343
+ #### If "Select by project":
344
+ List the projects with unimported sessions and ask:
345
+ ```json
346
+ {
347
+ "questions": [{
348
+ "header": "Project",
349
+ "question": "Which project's sessions should I import?",
350
+ "options": [
351
+ {"label": "ProjectA", "description": "5 unimported sessions"},
352
+ {"label": "ProjectB", "description": "7 unimported sessions"},
353
+ {"label": "Current directory", "description": "Import sessions from the current working directory"}
354
+ ],
355
+ "multiSelect": true
356
+ }]
357
+ }
358
+ ```
359
+
360
+ #### If "Select specific":
361
+ Show the session list with details (project path, turn count, last modified) and let user specify which ones. When user provides selection, map their choice back to session IDs.
362
+
363
+ ### Step 5: Execute Import
364
+
365
+ **IMPORTANT: Always use session_id for imports, NOT index numbers.** Index numbers can change between list operations and cause wrong sessions to be imported.
366
+
367
+ ```bash
368
+ # Import by session ID (PREFERRED - always use this)
369
+ aline watcher session import abc12345-6789-...
370
+
371
+ # Import multiple by session ID
372
+ aline watcher session import abc12345,def67890,ghi11111
373
+
374
+ # With force flag (re-import already tracked)
375
+ aline watcher session import abc12345 --force
376
+
377
+ # With regenerate flag (update summaries)
378
+ aline watcher session import abc12345 --regenerate
379
+
380
+ # Synchronous import to wait for completion
381
+ aline watcher session import abc12345 --sync
382
+ ```
383
+
384
+ For importing multiple sessions, collect all the session IDs from your analysis and pass them comma-separated.
385
+
386
+ ### Step 6: Verify Import Success
387
+
388
+ After import, check the status again:
389
+
390
+ ```bash
391
+ aline watcher session list --detect-turns
392
+ ```
393
+
394
+ Verify:
395
+ - Previously unimported sessions should now show as imported
396
+ - Check for any errors in the import output
397
+ - Report success/failure count to user
398
+
399
+ ### Step 7: Ask If User Is Satisfied
400
+
401
+ ```json
402
+ {
403
+ "questions": [{
404
+ "header": "Continue?",
405
+ "question": "Successfully imported X sessions. What would you like to do next?",
406
+ "options": [
407
+ {"label": "Import more", "description": "Select additional sessions to import"},
408
+ {"label": "View imported", "description": "Show details of imported sessions"},
409
+ {"label": "Done", "description": "Finish the import process"}
410
+ ],
411
+ "multiSelect": false
412
+ }]
413
+ }
414
+ ```
415
+
416
+ If user wants to import more, loop back to Step 1.
417
+
418
+ ### Step 8: Final Summary
419
+
420
+ When the user is done, provide a summary:
421
+ - Total sessions imported in this session
422
+ - Sessions that were updated
423
+ - Any errors encountered
424
+ - Next steps: suggest `aline search` to explore their imported history
425
+
426
+ ## Command Reference
427
+
428
+ | Command | Purpose |
429
+ |---------|---------|
430
+ | `aline watcher session list` | List all discovered sessions with status |
431
+ | `aline watcher session list --detect-turns` | Include turn counts in listing |
432
+ | `aline watcher session list -p N -n M` | Paginate: page N with M items per page |
433
+ | `aline watcher session import <session_id>` | Import by session ID (recommended) |
434
+ | `aline watcher session import <id1>,<id2>` | Import multiple sessions by ID |
435
+ | `aline watcher session import <id> -f` | Force re-import |
436
+ | `aline watcher session import <id> -r` | Regenerate LLM summaries |
437
+ | `aline watcher session import <id> --sync` | Synchronous import (wait for completion) |
438
+ | `aline watcher session show <session_id>` | View details of a specific session |
439
+
440
+ ## Important: Use Session IDs, Not Index Numbers
441
+
442
+ **Always use session_id (UUID) for import operations.**
443
+
444
+ Why:
445
+ - Index numbers are assigned dynamically and can change between list commands
446
+ - Using wrong index could import unintended sessions
447
+ - Session IDs are stable and unique identifiers
448
+
449
+ Example session ID format: `e58f67bf-ebba-47bd-9371-5ef9e06697d3`
450
+
451
+ You can use UUID prefix for convenience: `e58f67bf` (first 8 characters)
452
+
453
+ ## Error Handling
454
+
455
+ - **No sessions found**: Check if Claude Code history exists at `~/.claude/projects/`. Suggest running some Claude Code sessions first.
456
+ - **Import fails**: Check disk space, database permissions at `~/.aline/db/aline.db`
457
+ - **Partial import**: Some sessions may fail due to corrupted JSONL files. Report specific errors and suggest `--force` retry.
458
+ - **Watcher not running**: If async import doesn't complete, suggest `aline watcher start` or use `--sync` flag.
459
+
460
+ ## Tips for Large Imports
461
+
462
+ - For many sessions (50+), import in batches to track progress
463
+ - Use `--sync` flag to see real-time progress for smaller batches
464
+ - Check `~/.aline/.logs/watcher.log` for detailed import logs
465
+
466
+ ## When to Use This Skill
467
+
468
+ Use this skill when:
469
+ - User is setting up Aline for the first time
470
+ - User wants to import historical Claude Code sessions
471
+ - User asks "how do I import my history?" or similar
472
+ - User wants to selectively import specific project sessions
473
+ - User needs to re-import or update existing sessions
474
+ """
475
+
254
476
  # Registry of all skills to install
255
477
  SKILLS_REGISTRY: dict[str, str] = {
256
478
  "aline": ALINE_SKILL_MD,
257
479
  "aline-share": ALINE_SHARE_SKILL_MD,
480
+ "aline-import-history-sessions": ALINE_IMPORT_HISTORY_SESSIONS_SKILL_MD,
258
481
  }
259
482
 
260
483
 
@@ -293,32 +516,49 @@ def add_tmux_command() -> int:
293
516
  _source_aline_tmux_conf(tmux_conf)
294
517
 
295
518
  console.print(f"[green]✓[/green] tmux installed and config ready: [cyan]{tmux_conf}[/cyan]")
296
- console.print("[dim]Tip: in the Aline dashboard tmux session, mouse drag will copy to clipboard.[/dim]")
519
+ console.print(
520
+ "[dim]Tip: in the Aline dashboard tmux session, mouse drag will copy to clipboard.[/dim]"
521
+ )
297
522
  return 0
298
523
 
299
524
 
300
- def _install_skill_to_path(skill_root: Path, skill_name: str, skill_content: str) -> Path:
301
- """Install a skill to the specified root directory.
525
+ def _ensure_symlink(target_link: Path, source_file: Path, force: bool = False) -> bool:
526
+ """Create a symlink at target_link pointing to source_file.
302
527
 
303
528
  Args:
304
- skill_root: Root directory for skills (e.g., ~/.claude/skills)
305
- skill_name: Name of the skill (e.g., "aline")
306
- skill_content: Content of the SKILL.md file
529
+ target_link: The path where the symlink should be created (e.g., ~/.claude/skills/aline/SKILL.md)
530
+ source_file: The actual file to link to (e.g., ~/.aline/skills/aline/SKILL.md)
531
+ force: Whether to overwrite existing files/links
307
532
 
308
533
  Returns:
309
- Path to the installed SKILL.md file
534
+ True if a new link was created or updated, False if skipped (already exists)
310
535
  """
311
- skill_dir = skill_root / skill_name
312
- skill_dir.mkdir(parents=True, exist_ok=True)
313
- skill_file = skill_dir / "SKILL.md"
314
- skill_file.write_text(skill_content, encoding="utf-8")
315
- return skill_file
536
+ if target_link.exists() or target_link.is_symlink():
537
+ if not force:
538
+ # Check if it already points to the right place
539
+ try:
540
+ if target_link.is_symlink() and target_link.resolve() == source_file.resolve():
541
+ return False # Already correct
542
+ except Exception:
543
+ pass
544
+ return False # Exists and not forced
545
+
546
+ # Force: remove existing
547
+ if target_link.is_dir() and not target_link.is_symlink():
548
+ shutil.rmtree(target_link)
549
+ else:
550
+ target_link.unlink()
551
+
552
+ target_link.parent.mkdir(parents=True, exist_ok=True)
553
+ target_link.symlink_to(source_file)
554
+ return True
316
555
 
317
556
 
318
557
  def add_skills_command(force: bool = False) -> int:
319
- """Install Aline skills for Claude Code.
558
+ """Install Aline skills for Claude Code and Codex.
320
559
 
321
- Installs all skills from SKILLS_REGISTRY to ~/.claude/skills/
560
+ 1. Writes built-in skills to ~/.aline/skills/
561
+ 2. Creates symlinks in ~/.claude/skills/ and ~/.codex/skills/
322
562
 
323
563
  Args:
324
564
  force: Overwrite existing skills if they exist
@@ -326,42 +566,128 @@ def add_skills_command(force: bool = False) -> int:
326
566
  Returns:
327
567
  Exit code (0 for success, 1 for failure)
328
568
  """
329
- claude_skill_root = Path.home() / ".claude" / "skills"
569
+ aline_skill_root = Path.home() / ".aline" / "skills"
570
+ targets = [
571
+ ("Claude", Path.home() / ".claude" / "skills"),
572
+ ("Codex", Path.home() / ".codex" / "skills"),
573
+ ("OpenCode", Path.home() / ".config" / "opencode" / "skill"),
574
+ ]
575
+
330
576
  installed_skills: list[str] = []
331
577
  skipped_skills: list[str] = []
332
578
  failed_skills: list[tuple[str, str]] = []
333
579
 
334
580
  for skill_name, skill_content in SKILLS_REGISTRY.items():
335
- skill_path = claude_skill_root / skill_name / "SKILL.md"
336
-
337
- # Check if skill already exists
338
- if skill_path.exists() and not force:
339
- skipped_skills.append(skill_name)
340
- continue
341
-
581
+ # 1. Update master copy in ~/.aline/skills
582
+ master_path = aline_skill_root / skill_name / "SKILL.md"
342
583
  try:
343
- _install_skill_to_path(claude_skill_root, skill_name, skill_content)
344
- installed_skills.append(skill_name)
584
+ master_path.parent.mkdir(parents=True, exist_ok=True)
585
+ master_path.write_text(skill_content, encoding="utf-8")
345
586
  except Exception as e:
346
- failed_skills.append((skill_name, str(e)))
587
+ failed_skills.append((f"{skill_name} (storage)", str(e)))
588
+ continue
589
+
590
+ # 2. Link to targets
591
+ for tool_name, tool_root in targets:
592
+ dest_path = tool_root / skill_name / "SKILL.md"
593
+
594
+ try:
595
+ updated = _ensure_symlink(dest_path, master_path, force)
596
+ if updated:
597
+ installed_skills.append(f"{tool_name}/{skill_name}")
598
+ else:
599
+ skipped_skills.append(f"{tool_name}/{skill_name}")
600
+ except Exception as e:
601
+ failed_skills.append((f"{tool_name}/{skill_name}", str(e)))
347
602
 
348
603
  # Report results
349
- for skill_name in installed_skills:
350
- skill_path = claude_skill_root / skill_name / "SKILL.md"
351
- console.print(f"[green]✓[/green] Installed: [cyan]{skill_path}[/cyan]")
604
+ for item in installed_skills:
605
+ console.print(f"[green]✓[/green] Installed: [cyan]{item}[/cyan]")
352
606
 
353
- for skill_name in skipped_skills:
354
- skill_path = claude_skill_root / skill_name / "SKILL.md"
355
- console.print(f"[yellow]⊘[/yellow] Already exists: [dim]{skill_path}[/dim]")
607
+ for item in skipped_skills:
608
+ console.print(f"[yellow]⊘[/yellow] Already exists: [dim]{item}[/dim]")
356
609
 
357
- for skill_name, error in failed_skills:
358
- console.print(f"[red]✗[/red] Failed to install {skill_name}: {error}")
610
+ for item, error in failed_skills:
611
+ console.print(f"[red]✗[/red] Failed to install {item}: {error}")
359
612
 
360
613
  if skipped_skills and not installed_skills:
361
614
  console.print("[dim]Use --force to overwrite existing skills[/dim]")
362
615
  elif installed_skills:
363
- skill_names = ", ".join(f"/{s}" for s in installed_skills)
364
- console.print(f"[dim]Restart Claude Code to activate: {skill_names}[/dim]")
616
+ console.print("[dim]Restart your AI tools to activate new skills[/dim]")
365
617
 
366
618
  return 1 if failed_skills else 0
367
619
 
620
+
621
+ def add_skills_dev_command(force: bool = False) -> int:
622
+ """Install developer skills from skill-dev/ directory.
623
+
624
+ Symlinks skills from ./skill-dev/ to ~/.claude/skills/ and ~/.codex/skills/
625
+
626
+ Args:
627
+ force: Overwrite existing skills if they exist
628
+
629
+ Returns:
630
+ Exit code (0 for success, 1 for failure)
631
+ """
632
+ # Find skill-dev directory relative to this file's package location
633
+ package_root = Path(__file__).parent.parent.parent.parent
634
+ skill_dev_dir = package_root / "skill-dev"
635
+
636
+ if not skill_dev_dir.exists():
637
+ console.print(f"[red]skill-dev/ directory not found at:[/red] {skill_dev_dir}")
638
+ console.print("[dim]This command is for developer use only.[/dim]")
639
+ return 1
640
+
641
+ targets = [
642
+ ("Claude", Path.home() / ".claude" / "skills"),
643
+ ("Codex", Path.home() / ".codex" / "skills"),
644
+ ("OpenCode", Path.home() / ".config" / "opencode" / "skill"),
645
+ ]
646
+
647
+ installed_skills: list[str] = []
648
+ skipped_skills: list[str] = []
649
+ failed_skills: list[tuple[str, str]] = []
650
+
651
+ # Scan skill-dev for directories containing SKILL.md
652
+ for skill_dir in skill_dev_dir.iterdir():
653
+ if not skill_dir.is_dir():
654
+ continue
655
+
656
+ skill_file = skill_dir / "SKILL.md"
657
+ if not skill_file.exists():
658
+ continue
659
+
660
+ skill_name = skill_dir.name
661
+
662
+ for tool_name, tool_root in targets:
663
+ dest_path = tool_root / skill_name / "SKILL.md"
664
+
665
+ try:
666
+ updated = _ensure_symlink(dest_path, skill_file, force)
667
+ if updated:
668
+ installed_skills.append(f"{tool_name}/{skill_name}")
669
+ else:
670
+ skipped_skills.append(f"{tool_name}/{skill_name}")
671
+ except Exception as e:
672
+ failed_skills.append((f"{tool_name}/{skill_name}", str(e)))
673
+
674
+ if not installed_skills and not skipped_skills and not failed_skills:
675
+ console.print("[yellow]No skills found in skill-dev/[/yellow]")
676
+ return 0
677
+
678
+ # Report results
679
+ for item in installed_skills:
680
+ console.print(f"[green]✓[/green] Linked: [cyan]{item}[/cyan]")
681
+
682
+ for item in skipped_skills:
683
+ console.print(f"[yellow]⊘[/yellow] Already exists: [dim]{item}[/dim]")
684
+
685
+ for item, error in failed_skills:
686
+ console.print(f"[red]✗[/red] Failed to install {item}: {error}")
687
+
688
+ if skipped_skills and not installed_skills:
689
+ console.print("[dim]Use --force to overwrite existing skills[/dim]")
690
+ elif installed_skills:
691
+ console.print("[dim]Restart your AI tools to activate new skills[/dim]")
692
+
693
+ return 1 if failed_skills else 0
@@ -13,18 +13,9 @@ console = Console()
13
13
 
14
14
 
15
15
  def config_command(
16
- action: str = typer.Argument(
17
- ...,
18
- help="Action to perform: 'get', 'set', or 'init'"
19
- ),
20
- key: Optional[str] = typer.Argument(
21
- None,
22
- help="Configuration key (for get/set operations)"
23
- ),
24
- value: Optional[str] = typer.Argument(
25
- None,
26
- help="Configuration value (for set operation)"
27
- ),
16
+ action: str = typer.Argument(..., help="Action to perform: 'get', 'set', or 'init'"),
17
+ key: Optional[str] = typer.Argument(None, help="Configuration key (for get/set operations)"),
18
+ value: Optional[str] = typer.Argument(None, help="Configuration value (for set operation)"),
28
19
  ):
29
20
  """
30
21
  Manage ReAlign configuration.
@@ -146,7 +146,9 @@ def context_show_command(
146
146
  console.print("[dim]No active context.[/dim]")
147
147
  env_val = os.environ.get(CONTEXT_ID_ENV_VAR)
148
148
  if env_val:
149
- console.print(f"[dim]ALINE_CONTEXT_ID is set to '{env_val}' but no matching context found.[/dim]")
149
+ console.print(
150
+ f"[dim]ALINE_CONTEXT_ID is set to '{env_val}' but no matching context found.[/dim]"
151
+ )
150
152
  console.print("[dim]Searches will include all sessions/events.[/dim]")
151
153
  return 0
152
154