lite-kits 0.1.0__py3-none-any.whl → 0.3.1__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 (48) hide show
  1. lite_kits/__init__.py +61 -9
  2. lite_kits/cli.py +788 -262
  3. lite_kits/core/__init__.py +19 -0
  4. lite_kits/core/banner.py +160 -0
  5. lite_kits/core/conflict_checker.py +115 -0
  6. lite_kits/core/detector.py +140 -0
  7. lite_kits/core/installer.py +322 -0
  8. lite_kits/core/manifest.py +146 -0
  9. lite_kits/core/validator.py +146 -0
  10. lite_kits/kits/README.md +14 -15
  11. lite_kits/kits/dev/README.md +241 -0
  12. lite_kits/kits/dev/commands/.claude/audit.md +143 -0
  13. lite_kits/kits/{git/claude/commands → dev/commands/.claude}/cleanup.md +2 -2
  14. lite_kits/kits/{git/claude/commands → dev/commands/.claude}/commit.md +2 -2
  15. lite_kits/kits/{project/claude/commands → dev/commands/.claude}/orient.md +30 -48
  16. lite_kits/kits/{git/claude/commands → dev/commands/.claude}/pr.md +1 -1
  17. lite_kits/kits/dev/commands/.claude/review.md +202 -0
  18. lite_kits/kits/dev/commands/.claude/stats.md +162 -0
  19. lite_kits/kits/dev/commands/.github/audit.prompt.md +143 -0
  20. lite_kits/kits/{git/github/prompts → dev/commands/.github}/cleanup.prompt.md +2 -2
  21. lite_kits/kits/{git/github/prompts → dev/commands/.github}/commit.prompt.md +2 -2
  22. lite_kits/kits/{project/github/prompts → dev/commands/.github}/orient.prompt.md +34 -48
  23. lite_kits/kits/{git/github/prompts → dev/commands/.github}/pr.prompt.md +1 -1
  24. lite_kits/kits/dev/commands/.github/review.prompt.md +202 -0
  25. lite_kits/kits/dev/commands/.github/stats.prompt.md +163 -0
  26. lite_kits/kits/kits.yaml +497 -0
  27. lite_kits/kits/multiagent/README.md +28 -17
  28. lite_kits/kits/multiagent/{claude/commands → commands/.claude}/sync.md +331 -331
  29. lite_kits/kits/multiagent/{github/prompts → commands/.github}/sync.prompt.md +73 -69
  30. lite_kits/kits/multiagent/memory/git-worktrees-protocol.md +370 -370
  31. lite_kits/kits/multiagent/memory/parallel-work-protocol.md +536 -536
  32. lite_kits/kits/multiagent/memory/pr-workflow-guide.md +275 -281
  33. lite_kits/kits/multiagent/templates/collaboration-structure/README.md +166 -166
  34. lite_kits/kits/multiagent/templates/decision.md +79 -79
  35. lite_kits/kits/multiagent/templates/handoff.md +95 -95
  36. lite_kits/kits/multiagent/templates/session-log.md +68 -68
  37. lite_kits-0.3.1.dist-info/METADATA +259 -0
  38. lite_kits-0.3.1.dist-info/RECORD +41 -0
  39. {lite_kits-0.1.0.dist-info → lite_kits-0.3.1.dist-info}/licenses/LICENSE +21 -21
  40. lite_kits/installer.py +0 -417
  41. lite_kits/kits/git/README.md +0 -374
  42. lite_kits/kits/git/scripts/bash/get-git-context.sh +0 -208
  43. lite_kits/kits/git/scripts/powershell/Get-GitContext.ps1 +0 -242
  44. lite_kits/kits/project/README.md +0 -244
  45. lite_kits-0.1.0.dist-info/METADATA +0 -415
  46. lite_kits-0.1.0.dist-info/RECORD +0 -31
  47. {lite_kits-0.1.0.dist-info → lite_kits-0.3.1.dist-info}/WHEEL +0 -0
  48. {lite_kits-0.1.0.dist-info → lite_kits-0.3.1.dist-info}/entry_points.txt +0 -0
lite_kits/cli.py CHANGED
@@ -2,7 +2,7 @@
2
2
  """
3
3
  CLI for lite-kits
4
4
 
5
- Provides commands to add/remove enhancement kits for vanilla dev tools.
5
+ Lightweight enhancement kits for spec-driven development.
6
6
  """
7
7
 
8
8
  import sys
@@ -14,176 +14,299 @@ from rich.console import Console
14
14
  from rich.panel import Panel
15
15
  from rich.table import Table
16
16
 
17
- from . import __version__
18
- from .installer import Installer
19
-
20
- # Constants
21
- APP_NAME = "lite-kits"
22
- APP_DESCRIPTION = "Lightweight enhancement kits for vanilla dev tools"
23
- HELP_TIP = "Tip: Run 'lite-kits COMMAND --help' for detailed help on each command"
24
-
25
- # Kit names
26
- KIT_PROJECT = "project"
27
- KIT_GIT = "git"
28
- KIT_MULTIAGENT = "multiagent"
29
- KITS_ALL = [KIT_PROJECT, KIT_GIT, KIT_MULTIAGENT]
30
- KITS_RECOMMENDED = [KIT_PROJECT, KIT_GIT]
31
-
32
- # Help panel names
33
- PANEL_KIT_MANAGEMENT = "Kit Management"
34
- PANEL_PACKAGE_MANAGEMENT = "Package Management"
35
-
36
- # Kit descriptions
37
- KIT_DESC_PROJECT = "/orient command, project orientation features"
38
- KIT_DESC_GIT = "/commit, /pr, /cleanup commands with smart workflows"
39
- KIT_DESC_MULTIAGENT = "/sync, collaboration directories, memory guides"
40
-
41
- # Status indicators
42
- STATUS_OK = "[green][OK][/green]"
43
- STATUS_NOT_FOUND = "[dim][--][/dim]"
44
- STATUS_ERROR = "[red][X][/red]"
45
-
46
- # Marker files for kit detection
47
- MARKER_PROJECT_KIT = ".claude/commands/orient.md"
48
- MARKER_GIT_KIT = ".claude/commands/commit.md"
49
- MARKER_MULTIAGENT_KIT = ".specify/memory/pr-workflow-guide.md"
50
-
51
- # Error messages
52
- ERROR_NO_TARGET = "Either --here or a target directory must be specified"
53
- ERROR_NOT_SPEC_KIT = "does not appear to be a spec-kit project"
54
- ERROR_SPEC_KIT_HINT = "Looking for one of: .specify/, .claude/, or .github/prompts/"
17
+ from . import (
18
+ __version__,
19
+ APP_NAME,
20
+ APP_DESCRIPTION,
21
+ REPOSITORY_URL,
22
+ LICENSE,
23
+ KIT_DEV,
24
+ KIT_MULTIAGENT,
25
+ KITS_ALL,
26
+ KIT_DESC_DEV,
27
+ KIT_DESC_MULTIAGENT,
28
+ DIR_CLAUDE_COMMANDS,
29
+ DIR_GITHUB_PROMPTS,
30
+ DIR_SPECIFY_MEMORY,
31
+ DIR_SPECIFY_SCRIPTS_BASH,
32
+ DIR_SPECIFY_SCRIPTS_POWERSHELL,
33
+ DIR_SPECIFY_TEMPLATES,
34
+ ERROR_NOT_SPEC_KIT,
35
+ ERROR_SPEC_KIT_HINT,
36
+ )
37
+ from .core import diagonal_reveal_banner, show_loading_spinner, show_static_banner, Installer
55
38
 
56
39
  app = typer.Typer(
57
40
  name=APP_NAME,
58
- help=f"{APP_DESCRIPTION}\n\n[dim]{HELP_TIP}[/dim]",
59
- no_args_is_help=True,
60
- add_completion=False, # Disable shell completion to avoid modifying user profiles
61
- add_help_option=False, # Don't show --help in command help (redundant)
41
+ help=APP_DESCRIPTION, # Restore original description for --help
42
+ no_args_is_help=False, # We'll handle no-args case ourselves
43
+ add_completion=False,
62
44
  rich_markup_mode="rich",
63
45
  )
64
46
  console = Console()
65
47
 
48
+ def print_help_hint():
49
+ console.print(f"[dim]See [bold cyan]--help[/bold cyan] for all options and commands.[/dim]\n")
50
+
51
+ def print_version_info():
52
+ """Print version information."""
53
+ console.print(f"[bold]Version:[/bold]")
54
+ console.print(f" [bold cyan]{APP_NAME} version {__version__}[/bold cyan]\n")
55
+
56
+ def print_quick_start():
57
+ console.print("[bold]Quick Start:[/bold]")
58
+ console.print(f" [cyan]1. {APP_NAME} add[/cyan] # Add dev-kit (default)")
59
+ console.print(f" [cyan]2. {APP_NAME} status[/cyan] # Check installation")
60
+ console.print(f" [cyan]3. {APP_NAME} validate[/cyan] # Validate kit files\n")
61
+
62
+ def print_spec_kit_error():
63
+ """Print standardized spec-kit not found error message with installation instructions."""
64
+ console.print()
65
+ console.print(
66
+ f"[red]Error:[/red] {ERROR_NOT_SPEC_KIT}",
67
+ style="bold",
68
+ )
69
+ console.print(
70
+ f"\n {ERROR_SPEC_KIT_HINT}",
71
+ style="dim",
72
+ )
73
+ console.print("\n[bold yellow]lite-kits requires GitHub Spec-Kit:[/bold yellow]")
74
+ console.print(" lite-kits enhances vanilla spec-kit projects with additional commands.")
75
+ console.print(" You must install spec-kit first before adding lite-kits enhancements.\n")
76
+ console.print("[bold cyan]Install Spec-Kit:[/bold cyan]")
77
+ console.print(" 1. Install Node.js: https://nodejs.org/")
78
+ console.print(" 2. Install spec-kit: npm install -g @github/spec-kit")
79
+ console.print(" 3. Create project: specify init your-project-name")
80
+ console.print(" 4. More info: https://github.com/github/spec-kit\n")
81
+ console.print()
82
+
83
+ def print_kit_info(target_dir: Path, is_spec_kit: bool, installed_kits: list):
84
+ """Print kit installation info."""
85
+ console.print()
86
+ if is_spec_kit:
87
+ console.print(f"[bold green][OK] Spec-kit project detected in {target_dir}.[/bold green]\n")
88
+ if installed_kits:
89
+ console.print("Installed kits:", style="bold")
90
+ for kit in installed_kits:
91
+ console.print(f" [green]+[/green] {kit}-kit")
92
+ else:
93
+ console.print("No kits installed.", style="dim yellow")
94
+ else:
95
+ console.print(f"[bold red][X] {target_dir} is not a spec-kit project[/bold red]")
96
+ console.print(f" {ERROR_SPEC_KIT_HINT}", style="dim")
97
+ console.print()
66
98
 
67
99
  def version_callback(value: bool):
68
100
  """Print version and exit."""
69
101
  if value:
70
- console.print(f"{APP_NAME} version {__version__}")
102
+ console.print()
103
+ print_version_info()
104
+ console.print()
71
105
  raise typer.Exit()
72
106
 
73
-
74
- @app.callback()
107
+ @app.callback(invoke_without_command=True)
75
108
  def main(
109
+ ctx: typer.Context,
76
110
  version: Optional[bool] = typer.Option(
77
111
  None,
78
112
  "--version",
79
- "-v",
80
- help="Show version and exit",
113
+ "-V",
114
+ help="Display the lite-kits version",
81
115
  callback=version_callback,
82
116
  is_eager=True,
83
117
  ),
118
+ banner: Optional[bool] = typer.Option(
119
+ None,
120
+ "--banner",
121
+ help="Show the animated banner (can be combined with other commands)",
122
+ ),
123
+ quiet: Optional[bool] = typer.Option(
124
+ None,
125
+ "--quiet",
126
+ "-q",
127
+ help="Use quiet output",
128
+ ),
129
+ verbose: Optional[bool] = typer.Option(
130
+ None,
131
+ "--verbose",
132
+ "-v",
133
+ help="Use verbose output",
134
+ ),
135
+ directory: Optional[Path] = typer.Option(
136
+ None,
137
+ "--directory",
138
+ help="Change to the given directory prior to running the command",
139
+ ),
84
140
  ):
85
- f"""{APP_NAME}: {APP_DESCRIPTION}"""
86
- pass
87
-
88
-
89
- @app.command(name="add", rich_help_panel=PANEL_KIT_MANAGEMENT)
141
+ """Main CLI entry point."""
142
+ if directory:
143
+ import os
144
+ os.chdir(directory)
145
+
146
+ # Store banner flag in context for commands to use
147
+ ctx.obj = {"show_banner": banner}
148
+
149
+ # Show banner if requested
150
+ if banner:
151
+ try:
152
+ diagonal_reveal_banner()
153
+ except UnicodeEncodeError:
154
+ # Windows console doesn't support Unicode box characters
155
+ console.print("[bold cyan]LITE-KITS[/bold cyan]")
156
+ console.print("[dim]Lightweight enhancement kits for spec-driven development[/dim]\n")
157
+
158
+ # Show banner + hint and quick-start when no command is given
159
+ if ctx.invoked_subcommand is None:
160
+ if not banner: # Only show static banner if animated not already shown
161
+ show_static_banner()
162
+ print_help_hint()
163
+ print_quick_start()
164
+
165
+ @app.command(hidden=True)
166
+ def help(ctx: typer.Context):
167
+ """Show help information (alias for --help)."""
168
+ console.print(ctx.parent.get_help())
169
+
170
+ @app.command(name="add")
90
171
  def add_kits(
91
- here: bool = typer.Option(
92
- False,
93
- "--here",
94
- help="Add to current directory",
172
+ kit: Optional[str] = typer.Option(
173
+ None,
174
+ "--kit",
175
+ help=f"Comma-separated list of kits to add: {','.join(KITS_ALL)}",
95
176
  ),
96
- dry_run: bool = typer.Option(
177
+ all_kits: bool = typer.Option(
97
178
  False,
98
- "--dry-run",
99
- help="Preview changes without applying them",
179
+ "--all",
180
+ help="Add all kits",
100
181
  ),
101
- kit: Optional[str] = typer.Option(
182
+ agent: Optional[str] = typer.Option(
102
183
  None,
103
- "--kit",
104
- help=f"Comma-separated list of kits to add: {','.join(KITS_ALL)} (default: {KIT_PROJECT})",
184
+ "--agent",
185
+ help="Explicit agent preference (claude, copilot, etc.)",
105
186
  ),
106
- recommended: bool = typer.Option(
187
+ shell: Optional[str] = typer.Option(
188
+ None,
189
+ "--shell",
190
+ help="Explicit shell preference (bash, powershell)",
191
+ ),
192
+ verbose: bool = typer.Option(
193
+ False,
194
+ "--verbose",
195
+ "-v",
196
+ help="Show detailed file listings in preview",
197
+ ),
198
+ force: bool = typer.Option(
107
199
  False,
108
- "--recommended",
109
- help=f"Add recommended kits: {' + '.join(KITS_RECOMMENDED)}",
200
+ "--force",
201
+ help="Skip preview and confirmations, overwrite existing files",
110
202
  ),
111
203
  target: Optional[Path] = typer.Argument(
112
204
  None,
113
205
  help="Target directory (defaults to current directory)",
114
206
  ),
115
207
  ):
116
- """Add enhancement kits to a spec-kit project."""
117
- if not here and target is None:
118
- console.print(
119
- f"[red]Error:[/red] {ERROR_NO_TARGET}",
120
- style="bold",
121
- )
122
- raise typer.Exit(1)
208
+ """Add enhancement kits to a spec-kit project.
123
209
 
124
- target_dir = Path.cwd() if here else target
210
+ Shows a preview of changes before installation and asks for confirmation.
211
+ Use --verbose/-v to see detailed file listings.
212
+ Use --force to skip preview and install immediately.
213
+ """
214
+ target_dir = Path.cwd() if target is None else target
125
215
 
126
216
  # Determine which kits to install
127
217
  kits = None
128
- if recommended:
129
- kits = KITS_RECOMMENDED
218
+ if all_kits:
219
+ kits = KITS_ALL
130
220
  elif kit:
131
221
  kits = [k.strip() for k in kit.split(',')]
132
- # else: kits=None will use default [KIT_PROJECT] in Installer
222
+ # else: kits=None will use default from manifest
133
223
 
134
224
  try:
135
- installer = Installer(target_dir, kits=kits)
225
+ installer = Installer(
226
+ target_dir,
227
+ kits=kits,
228
+ force=force,
229
+ agent=agent,
230
+ shell=shell
231
+ )
136
232
  except ValueError as e:
233
+ console.print()
137
234
  console.print(f"[red]Error:[/red] {e}", style="bold")
235
+ console.print()
138
236
  raise typer.Exit(1)
139
237
 
140
238
  # Validate target is a spec-kit project
141
239
  if not installer.is_spec_kit_project():
142
- console.print(
143
- f"[red]Error:[/red] {target_dir} {ERROR_NOT_SPEC_KIT}",
144
- style="bold",
145
- )
146
- console.print(
147
- f"\n{ERROR_SPEC_KIT_HINT}",
148
- style="dim",
149
- )
240
+ print_spec_kit_error()
150
241
  raise typer.Exit(1)
151
242
 
152
- # Check if already installed
153
- if installer.is_multiagent_installed():
243
+ # Check if already installed (check for dev-kit as default)
244
+ skip_preview = force # Track if we should skip preview (only when --force flag used)
245
+ reinstalling = False
246
+
247
+ if installer.is_kit_installed(KIT_DEV):
248
+ console.print()
154
249
  console.print(
155
250
  "[yellow]Warning:[/yellow] Enhancement kits appear to be already installed",
156
251
  style="bold",
157
252
  )
158
- if not typer.confirm("Reinstall anyway?"):
253
+ if not force:
254
+ if not typer.confirm("Reinstall anyway?"):
255
+ console.print()
256
+ raise typer.Exit(0)
257
+ # User confirmed reinstall - mark as reinstalling but still show preview
258
+ reinstalling = True
259
+
260
+ # Always show preview unless --force flag was used
261
+ if not skip_preview:
262
+ console.print(f"\n[bold magenta]Previewing changes for:[/bold magenta]\n[bold yellow]{target_dir}[/bold yellow]\n")
263
+ preview = installer.preview_installation()
264
+ normalized_preview = _normalize_preview_for_display(preview, operation="install")
265
+ _display_changes(normalized_preview, verbose=verbose)
266
+
267
+ # Show warnings/conflicts
268
+ if preview.get("warnings"):
269
+ console.print("\n[bold yellow]Warnings:[/bold yellow]")
270
+ for warning in preview["warnings"]:
271
+ console.print(f" ⚠ {warning}")
272
+
273
+ if preview.get("conflicts"):
274
+ console.print("\n[bold yellow]Conflicts (will overwrite):[/bold yellow]")
275
+ for conflict in preview["conflicts"]:
276
+ console.print(f" ⚠ {conflict['path']}")
277
+
278
+ # Ask for confirmation
279
+ console.print()
280
+ if not typer.confirm("Proceed with installation?"):
281
+ console.print("[dim]Installation cancelled[/dim]")
282
+ console.print()
159
283
  raise typer.Exit(0)
160
284
 
161
- # Preview or install
162
- if dry_run:
163
- console.print("\n[bold cyan]Dry run - no changes will be made[/bold cyan]\n")
164
- changes = installer.preview_installation()
165
- _display_changes(changes)
166
- else:
167
- console.print(f"\n[bold green]Adding enhancement kits to {target_dir}[/bold green]\n")
168
-
169
- with console.status("[bold green]Adding kits..."):
170
- result = installer.install()
285
+ # User confirmed preview - enable force to bypass conflict checks during install
286
+ # (This recreates installer with force=True to skip conflict detection)
287
+ installer = Installer(
288
+ target_dir,
289
+ kits=kits,
290
+ force=True, # Skip conflict checks after user confirmed
291
+ agent=agent,
292
+ shell=shell
293
+ )
171
294
 
172
- if result["success"]:
173
- console.print("\n[bold green][OK] Kits added successfully![/bold green]\n")
174
- _display_installation_summary(result)
175
- else:
176
- console.print(f"\n[bold red][X] Failed to add kits:[/bold red] {result['error']}\n")
177
- raise typer.Exit(1)
295
+ # Install
296
+ console.print(f"\n[bold green]Installing kits to {target_dir}[/bold green]\n")
297
+ show_loading_spinner("Installing...")
298
+ result = installer.install()
178
299
 
300
+ if result["success"]:
301
+ _display_installation_summary(result, verbose=verbose)
302
+ console.print("[bold green][OK] Kits installed successfully![/bold green]\n")
303
+ else:
304
+ console.print(f"\n[bold red][X] Installation failed:[/bold red] {result['error']}\n")
305
+ console.print()
306
+ raise typer.Exit(1)
179
307
 
180
- @app.command(rich_help_panel=PANEL_KIT_MANAGEMENT)
308
+ @app.command()
181
309
  def remove(
182
- here: bool = typer.Option(
183
- False,
184
- "--here",
185
- help="Remove from current directory",
186
- ),
187
310
  kit: Optional[str] = typer.Option(
188
311
  None,
189
312
  "--kit",
@@ -194,29 +317,36 @@ def remove(
194
317
  "--all",
195
318
  help="Remove all kits",
196
319
  ),
320
+ verbose: bool = typer.Option(
321
+ False,
322
+ "--verbose",
323
+ "-v",
324
+ help="Show detailed file listings in preview",
325
+ ),
326
+ force: bool = typer.Option(
327
+ False,
328
+ "--force",
329
+ help="Skip preview and confirmations",
330
+ ),
197
331
  target: Optional[Path] = typer.Argument(
198
332
  None,
199
333
  help="Target directory (defaults to current directory)",
200
334
  ),
201
335
  ):
202
- """
203
- Remove enhancement kits from a spec-kit project.
336
+ """Remove enhancement kits from a spec-kit project.
204
337
 
205
- Returns the project to vanilla spec-kit state.
338
+ Removes kit files and returns the project to vanilla spec-kit state.
339
+ Shows preview of files to be removed before confirmation.
340
+ Use --verbose/-v to see detailed file listings.
341
+ Use --force to skip preview and remove immediately.
206
342
 
207
343
  Examples:
208
- lite-kits remove --here --kit git # Remove git-kit only
209
- lite-kits remove --here --kit project,git # Remove specific kits
210
- lite-kits remove --here --all # Remove all kits
344
+ lite-kits remove --kit dev # Remove dev-kit
345
+ lite-kits remove --kit dev,multiagent # Remove multiple kits
346
+ lite-kits remove --all # Remove all kits
347
+ lite-kits remove --all --force # Remove all kits without confirmation
211
348
  """
212
- if not here and target is None:
213
- console.print(
214
- "[red]Error:[/red] Either --here or a target directory must be specified",
215
- style="bold",
216
- )
217
- raise typer.Exit(1)
218
-
219
- target_dir = Path.cwd() if here else target
349
+ target_dir = Path.cwd() if target is None else target
220
350
 
221
351
  # Determine which kits to remove
222
352
  kits = None
@@ -225,247 +355,612 @@ def remove(
225
355
  elif kit:
226
356
  kits = [k.strip() for k in kit.split(',')]
227
357
  else:
358
+ console.print()
228
359
  console.print("[yellow]Error:[/yellow] Specify --kit or --all", style="bold")
229
360
  console.print("\nExamples:", style="dim")
230
- console.print(f" {APP_NAME} remove --here --kit {KIT_GIT}", style="dim")
231
- console.print(f" {APP_NAME} remove --here --all", style="dim")
361
+ console.print(f" {APP_NAME} remove --kit {KIT_DEV}", style="dim")
362
+ console.print(f" {APP_NAME} remove --all", style="dim")
363
+ console.print()
232
364
  raise typer.Exit(1)
233
365
 
234
366
  try:
235
367
  installer = Installer(target_dir, kits=kits)
236
368
  except ValueError as e:
369
+ console.print()
237
370
  console.print(f"[red]Error:[/red] {e}", style="bold")
371
+ console.print()
238
372
  raise typer.Exit(1)
239
373
 
240
- # Check if kits are installed
241
- if not installer.is_multiagent_installed():
374
+ # Filter to only actually installed kits
375
+ installed_kits = [k for k in kits if installer.is_kit_installed(k)]
376
+ if not installed_kits:
377
+ console.print()
242
378
  console.print("[yellow]Warning:[/yellow] No kits detected to remove", style="bold")
379
+ console.print()
243
380
  raise typer.Exit(0)
244
381
 
245
- # Confirm removal
246
- console.print(f"\n[bold yellow]Remove kits from {target_dir}[/bold yellow]")
247
- console.print(f"Kits to remove: {', '.join(kits)}\n")
382
+ # Update installer with filtered list
383
+ installer = Installer(target_dir, kits=installed_kits)
248
384
 
249
- if not typer.confirm("Continue with removal?"):
250
- console.print("Cancelled")
251
- raise typer.Exit(0)
385
+ # Show preview and confirmation unless --force is used
386
+ if not force:
387
+ # Show preview of files to be removed
388
+ console.print(f"\n[bold magenta]Previewing changes for:[/bold magenta]\n[bold yellow]{target_dir}[/bold yellow]\n")
389
+ preview = installer.preview_removal()
390
+
391
+ if preview["total_files"] == 0:
392
+ console.print("[dim]No files found to remove[/dim]")
393
+ console.print()
394
+ raise typer.Exit(0)
395
+
396
+ # Normalize removal preview to standard format for DRY display
397
+ normalized_preview = _normalize_preview_for_display(preview, operation="remove")
398
+ _display_changes(normalized_preview, verbose=verbose)
399
+
400
+ # Confirm removal
401
+ if not typer.confirm("Continue with removal?"):
402
+ console.print("[dim]Cancelled[/dim]")
403
+ console.print()
404
+ raise typer.Exit(0)
252
405
 
253
406
  # Remove kits
254
- console.print("\n[bold]Removing kits...[/bold]\n")
255
- with console.status("[bold yellow]Removing..."):
256
- result = installer.remove()
407
+ console.print(f"\n[bold yellow]Removing files...[/bold yellow]")
408
+ result = installer.remove()
257
409
 
258
410
  if result["success"]:
259
- console.print("[bold green]Removal complete![/bold green]\n")
260
- if result["removed"]:
261
- console.print("[bold]Removed:[/bold]")
262
- for item in result["removed"]:
263
- console.print(f" - {item}")
264
- else:
265
- console.print("[dim]No files found to remove[/dim]")
411
+ _display_removal_summary(result, verbose=verbose)
412
+
413
+ # Clean up empty directories
414
+ _cleanup_empty_directories(target_dir)
415
+ console.print("\n[bold green][OK] Removal complete![/bold green]\n")
266
416
  else:
267
- console.print(f"\n[bold red]Removal failed:[/bold red] {result['error']}\n")
417
+ console.print(f"\n[bold red][X] Removal failed:[/bold red] {result['error']}\n")
418
+ console.print()
268
419
  raise typer.Exit(1)
269
420
 
270
-
271
- @app.command(rich_help_panel=PANEL_KIT_MANAGEMENT)
421
+ @app.command()
272
422
  def validate(
273
- here: bool = typer.Option(
274
- False,
275
- "--here",
276
- help="Validate current directory",
277
- ),
278
423
  target: Optional[Path] = typer.Argument(
279
424
  None,
280
425
  help="Target directory (defaults to current directory)",
281
426
  ),
282
427
  ):
283
- """
284
- Validate enhancement kit installation.
428
+ """Validate enhancement kit installation integrity.
285
429
 
286
430
  Checks:
287
- - Kit files are present and correctly installed
288
- - Collaboration directory structure (if multiagent-kit installed)
289
- - Required files present
290
- - Cross-kit consistency
431
+ - All required kit files are present
432
+ - Files are not corrupted or empty
433
+ - Kit structure is correct
434
+ - Collaboration directories (for multiagent-kit)
291
435
 
292
436
  Example:
293
- lite-kits validate --here
437
+ lite-kits validate # Validate current directory
438
+ lite-kits validate path/to/dir # Validate specific directory
294
439
  """
295
- # Default to current directory if no target specified
296
- if here or target is None:
297
- target_dir = Path.cwd()
298
- else:
299
- target_dir = target
440
+ target_dir = Path.cwd() if target is None else target
300
441
 
301
442
  # For validation, we don't know which kits are installed yet, so check for all
302
443
  installer = Installer(target_dir, kits=KITS_ALL)
303
444
 
304
- console.print(f"\n[bold cyan]Validating {target_dir}[/bold cyan]\n")
305
-
306
- # Check if it's a spec-kit project
445
+ # Check if it's a spec-kit project first
307
446
  if not installer.is_spec_kit_project():
308
- console.print("[red][X] Not a spec-kit project[/red]")
447
+ print_spec_kit_error()
309
448
  raise typer.Exit(1)
310
449
 
311
450
  # Check if any kits are installed
312
- if not installer.is_multiagent_installed():
451
+ any_installed = any(installer.is_kit_installed(k) for k in KITS_ALL)
452
+ if not any_installed:
453
+ console.print()
313
454
  console.print("[yellow]⚠ No enhancement kits installed[/yellow]")
314
- console.print(f" Run: {APP_NAME} add --here --recommended", style="dim")
455
+ console.print(f" Run: {APP_NAME} add", style="dim")
456
+ console.print()
315
457
  raise typer.Exit(1)
316
458
 
317
459
  # Validate structure
318
- with console.status("[bold cyan]Validating..."):
319
- validation_result = installer.validate()
320
-
460
+ console.print(f"\n[bold cyan]Validating {target_dir}[/bold cyan]\n")
461
+ validation_result = installer.validate()
321
462
  _display_validation_results(validation_result)
322
463
 
323
464
  if validation_result["valid"]:
324
- console.print("\n[bold green][OK] Validation passed![/bold green]")
465
+ console.print("\n[bold green][OK] Validation passed![/bold green]\n")
325
466
  raise typer.Exit(0)
326
467
  else:
327
- console.print("\n[bold red][X] Validation failed[/bold red]")
468
+ console.print("\n[bold red][X] Validation failed[/bold red]\n")
328
469
  raise typer.Exit(1)
329
470
 
330
-
331
- @app.command(rich_help_panel=PANEL_KIT_MANAGEMENT)
471
+ @app.command()
332
472
  def status(
333
- here: bool = typer.Option(
334
- False,
335
- "--here",
336
- help="Check current directory",
337
- ),
338
473
  target: Optional[Path] = typer.Argument(
339
474
  None,
340
475
  help="Target directory (defaults to current directory)",
341
476
  ),
342
477
  ):
343
- """
344
- Show enhancement kit installation status for the project.
478
+ """Show enhancement kit installation status.
345
479
 
346
480
  Displays:
347
- - Spec-kit project detection
348
- - Installed kits
349
- - Installation health
481
+ - Whether directory is a spec-kit project
482
+ - Which kits are installed (dev, multiagent)
483
+ - Quick summary of installation state
350
484
 
351
485
  Example:
352
- lite-kits status --here
486
+ lite-kits status # Check current directory
487
+ lite-kits status path/to/dir # Check specific directory
353
488
  """
354
- # Default to current directory if no target specified
355
- if here or target is None:
356
- target_dir = Path.cwd()
357
- else:
358
- target_dir = target
489
+ target_dir = Path.cwd() if target is None else target
359
490
 
360
491
  # For status, check for all possible kits
361
492
  installer = Installer(target_dir, kits=KITS_ALL)
362
493
 
363
- console.print(f"\n[bold cyan]Project Status: {target_dir}[/bold cyan]\n")
364
-
365
494
  # Basic checks
366
495
  is_spec_kit = installer.is_spec_kit_project()
367
496
 
368
- # Check individual kits
369
- project_kit_installed = (target_dir / MARKER_PROJECT_KIT).exists()
370
- git_kit_installed = (target_dir / MARKER_GIT_KIT).exists()
371
- multiagent_kit_installed = (target_dir / MARKER_MULTIAGENT_KIT).exists()
372
-
373
- table = Table(show_header=False, box=None)
374
- table.add_column("Item", style="cyan")
375
- table.add_column("Status")
497
+ # Check individual kits using the installer's validator
498
+ installed_kits = []
499
+ for kit_name in KITS_ALL:
500
+ if installer.is_kit_installed(kit_name):
501
+ installed_kits.append(kit_name)
376
502
 
377
- table.add_row("Spec-kit project", STATUS_OK if is_spec_kit else STATUS_ERROR)
378
- table.add_row(f"{KIT_PROJECT}-kit", STATUS_OK if project_kit_installed else STATUS_NOT_FOUND)
379
- table.add_row(f"{KIT_GIT}-kit", STATUS_OK if git_kit_installed else STATUS_NOT_FOUND)
380
- table.add_row(f"{KIT_MULTIAGENT}-kit", STATUS_OK if multiagent_kit_installed else STATUS_NOT_FOUND)
503
+ # Show kit info (skip banner to avoid Windows console Unicode issues)
504
+ print_kit_info(target_dir, is_spec_kit, installed_kits)
381
505
 
382
- console.print(table)
383
- console.print()
506
+ def _normalize_preview_for_display(preview: dict, operation: str = "install") -> dict:
507
+ """Normalize preview data to standard format for display.
384
508
 
509
+ Converts both installation and removal previews to a unified format
510
+ that _display_changes can handle.
385
511
 
386
- def _display_changes(changes: dict):
387
- """Display preview of changes."""
388
- console.print("[bold]Files to be created:[/bold]")
389
- for file in changes.get("new_files", []):
390
- console.print(f" [green]+[/green] {file}")
512
+ Args:
513
+ preview: Raw preview dict from installer (preview_installation or preview_removal)
514
+ operation: "install" or "remove" to determine how to process the preview
391
515
 
392
- console.print("\n[bold]Files to be modified:[/bold]")
393
- for file in changes.get("modified_files", []):
394
- console.print(f" [yellow]~[/yellow] {file}")
516
+ Returns:
517
+ Normalized preview dict with standard keys (new_files, modified_files,
518
+ files_to_remove, new_directories, directories_to_remove)
519
+ """
520
+ if operation == "install":
521
+ # Installation preview is already in the right format
522
+ return preview
523
+ elif operation == "remove":
524
+ # Removal preview needs conversion
525
+ normalized = {"kits": []}
526
+ for kit in preview.get("kits", []):
527
+ # Calculate unique directories that will be affected
528
+ directories = set()
529
+ for file_path in kit.get("files", []):
530
+ parent = str(Path(file_path).parent)
531
+ if parent and parent != ".":
532
+ directories.add(parent)
533
+
534
+ normalized["kits"].append({
535
+ "name": kit["name"],
536
+ "new_files": [],
537
+ "modified_files": [],
538
+ "files_to_remove": kit["files"],
539
+ "new_directories": [],
540
+ "directories_to_remove": sorted(directories)
541
+ })
542
+ return normalized
543
+ else:
544
+ raise ValueError(f"Unknown operation: {operation}")
395
545
 
396
- console.print("\n[bold]Directories to be created:[/bold]")
397
- for dir in changes.get("new_directories", []):
398
- console.print(f" [blue]+[/blue] {dir}")
546
+ def _display_changes(changes: dict, verbose: bool = False):
547
+ """Display preview of changes.
399
548
 
549
+ Args:
550
+ changes: Normalized preview dict with file/directory changes
551
+ verbose: If True, show detailed file listings; if False, show only tables
552
+ """
553
+ from collections import defaultdict
554
+
555
+ # Collect stats for each kit
556
+ kit_stats = {}
557
+ total_stats = defaultdict(int)
558
+
559
+ for kit in changes.get("kits", []):
560
+ kit_name = kit.get("name", "Unknown Kit")
561
+ stats = defaultdict(int)
562
+
563
+ # Count files by type based on path (normalize to forward slashes for matching)
564
+ # Include new_files, modified_files, and files_to_remove
565
+ all_files = (
566
+ kit.get("new_files", []) +
567
+ kit.get("modified_files", []) +
568
+ kit.get("files_to_remove", [])
569
+ )
400
570
 
401
- def _display_installation_summary(result: dict):
402
- """Display kit addition summary."""
403
- console.print("[bold]Added:[/bold]")
404
- for item in result.get("installed", []):
405
- console.print(f" [OK] {item}")
571
+ for file_path in all_files:
572
+ # Normalize path separators for cross-platform matching
573
+ normalized_path = str(file_path).replace("\\", "/")
574
+
575
+ # Track by file type
576
+ if "/commands/" in normalized_path or "/prompts/" in normalized_path:
577
+ stats["commands"] += 1
578
+ elif "/scripts/" in normalized_path:
579
+ stats["scripts"] += 1
580
+ elif "/memory/" in normalized_path:
581
+ stats["memory"] += 1
582
+ elif "/templates/" in normalized_path:
583
+ stats["templates"] += 1
584
+ else:
585
+ stats["other"] += 1
586
+
587
+ # Track by agent (for agent breakdown table)
588
+ if ".claude/" in normalized_path:
589
+ stats["agent_claude"] += 1
590
+ elif ".github/" in normalized_path:
591
+ stats["agent_copilot"] += 1
592
+ else:
593
+ stats["agent_shared"] += 1
594
+
595
+ # Count directories (both new and to-be-removed)
596
+ stats["directories"] = len(kit.get("new_directories", [])) + len(kit.get("directories_to_remove", []))
597
+
598
+ # Total files (excluding directories)
599
+ stats["files"] = len(all_files)
600
+
601
+ kit_stats[kit_name.lower().replace(" kit", "")] = stats
602
+
603
+ # Accumulate totals
604
+ for key, value in stats.items():
605
+ total_stats[key] += value
606
+
607
+ # Display kit details (only if verbose)
608
+ if verbose:
609
+ for kit in changes.get("kits", []):
610
+ kit_name = kit.get("name", "Unknown Kit")
611
+ kit_key = kit_name.lower().replace(" kit", "")
612
+ stats = kit_stats[kit_key]
613
+
614
+ console.print(f"[bold magenta]=== {kit_name} ===[/bold magenta]")
615
+
616
+ # Only show sections that have items
617
+ new_files = kit.get("new_files", [])
618
+ modified_files = kit.get("modified_files", [])
619
+ files_to_remove = kit.get("files_to_remove", [])
620
+ new_directories = kit.get("new_directories", [])
621
+
622
+ if new_files:
623
+ console.print("Files to be created:")
624
+ for file in new_files:
625
+ console.print(f" [green]+[/green] {file}")
626
+ console.print() # Blank line after section
627
+
628
+ if modified_files:
629
+ console.print("Files to be modified:")
630
+ for file in modified_files:
631
+ console.print(f" [yellow]~[/yellow] {file}")
632
+ console.print() # Blank line after section
633
+
634
+ if files_to_remove:
635
+ console.print("Files to be removed:")
636
+ for file in files_to_remove:
637
+ console.print(f" [red]-[/red] {file}")
638
+ console.print() # Blank line after section
639
+
640
+ if new_directories:
641
+ console.print("Directories to be created:")
642
+ for dir in new_directories:
643
+ console.print(f" [blue]+[/blue] {dir}")
644
+ console.print() # Blank line after section
645
+
646
+ # Show kit summary
647
+ summary_parts = []
648
+ if stats["files"] > 0:
649
+ summary_parts.append(f"{stats['files']} files")
650
+ if stats["directories"] > 0:
651
+ summary_parts.append(f"{stats['directories']} directory")
652
+ if summary_parts:
653
+ console.print(f"{kit_name}: {', '.join(summary_parts)}")
654
+ console.print() # Blank line after kit
655
+
656
+ # Display summary tables
657
+ _display_preview_tables(kit_stats, changes)
658
+
659
+ def _display_preview_tables(kit_stats: dict, changes: dict):
660
+ """Display preview summary tables with color-coded values.
661
+
662
+ Colors:
663
+ - Green +N: Additions (new files)
664
+ - Yellow ~N: Modifications (changed files)
665
+ - Red -N: Removals (deleted files)
666
+ - White 0: No changes
667
+ """
668
+ from rich.box import ROUNDED
669
+
670
+ if not kit_stats:
671
+ return
672
+
673
+ # Helper to format kit names
674
+ def format_kit_name(kit_key: str) -> str:
675
+ return "Dev Kit" if kit_key == "dev" else "Multiagent Kit"
676
+
677
+ # Helper to style values based on operation type
678
+ def style_value(value: int, is_addition: bool = True, is_modification: bool = False) -> str:
679
+ """Style numeric values with colors and prefix symbols."""
680
+ if value == 0:
681
+ return "0"
682
+ elif is_modification:
683
+ return f"[yellow]~{value}[/yellow]"
684
+ elif is_addition:
685
+ return f"[green]+{value}[/green]"
686
+ else: # removal
687
+ return f"[red]-{value}[/red]"
688
+
689
+ # Determine operation type from changes dict
690
+ has_new_files = any(kit.get("new_files") for kit in changes.get("kits", []))
691
+ has_modified_files = any(kit.get("modified_files") for kit in changes.get("kits", []))
692
+ is_removal = not has_new_files and not has_modified_files # If no new/modified, it's a removal
693
+
694
+ # Agent breakdown table (show which agents get which files)
695
+ agent_categories = ["agent_claude", "agent_copilot", "agent_shared"]
696
+ has_agent_breakdown = any(
697
+ any(stats.get(cat, 0) > 0 for cat in agent_categories)
698
+ for stats in kit_stats.values()
699
+ )
700
+
701
+ if has_agent_breakdown:
702
+ console.print("[bold magenta]Agent Breakdown:[/bold magenta]")
703
+ table = Table(show_header=True, header_style="bold cyan", box=ROUNDED)
704
+ table.add_column("Agent", style="cyan", no_wrap=True)
705
+
706
+ # Add kit columns
707
+ for kit_key in kit_stats.keys():
708
+ table.add_column(format_kit_name(kit_key), justify="right")
709
+
710
+ # Add Total column if multiple kits
711
+ show_total = len(kit_stats) > 1
712
+ if show_total:
713
+ table.add_column("Total", justify="right")
714
+
715
+ # Add rows (only agents with non-zero values)
716
+ agent_labels = {
717
+ "agent_claude": "Claude Code",
718
+ "agent_copilot": "GitHub Copilot",
719
+ "agent_shared": "Shared"
720
+ }
721
+
722
+ for cat in agent_categories:
723
+ values = [kit_stats[kit_key].get(cat, 0) for kit_key in kit_stats.keys()]
724
+ if sum(values) > 0: # Only show row if at least one kit has files for this agent
725
+ styled_values = [style_value(v, not is_removal, has_modified_files) for v in values]
726
+ row = [agent_labels[cat]] + styled_values
727
+ if show_total:
728
+ row.append(style_value(sum(values), not is_removal, has_modified_files))
729
+ table.add_row(*row)
730
+
731
+ console.print(table)
732
+ console.print()
733
+
734
+ # Kit contents table (Commands, Scripts, Memory, Templates)
735
+ file_type_categories = ["commands", "scripts", "memory", "templates"]
736
+ has_file_types = any(
737
+ any(stats.get(cat, 0) > 0 for cat in file_type_categories)
738
+ for stats in kit_stats.values()
739
+ )
740
+
741
+ if has_file_types:
742
+ console.print("[bold magenta]Kit Contents:[/bold magenta]")
743
+ table = Table(show_header=True, header_style="bold cyan", box=ROUNDED)
744
+ table.add_column("Type", style="cyan", no_wrap=True)
745
+
746
+ # Add kit columns (only kits that have file types)
747
+ active_kits = [
748
+ kit_key for kit_key in kit_stats.keys()
749
+ if any(kit_stats[kit_key].get(cat, 0) > 0 for cat in file_type_categories)
750
+ ]
751
+
752
+ for kit_key in active_kits:
753
+ table.add_column(format_kit_name(kit_key), justify="right")
754
+
755
+ # Add Total column if multiple kits
756
+ show_total = len(active_kits) > 1
757
+ if show_total:
758
+ table.add_column("Total", justify="right")
759
+
760
+ # Add rows (only categories with non-zero values)
761
+ for cat in file_type_categories:
762
+ values = [kit_stats[kit_key].get(cat, 0) for kit_key in active_kits]
763
+ if sum(values) > 0: # Only show row if at least one kit has this type
764
+ styled_values = [style_value(v, not is_removal, has_modified_files) for v in values]
765
+ row = [cat.capitalize()] + styled_values
766
+ if show_total:
767
+ row.append(style_value(sum(values), not is_removal, has_modified_files))
768
+ table.add_row(*row)
769
+
770
+ console.print(table)
771
+ console.print()
772
+
773
+ # Totals table (Files and Directories)
774
+ console.print("[bold magenta]File Totals:[/bold magenta]")
775
+ table = Table(show_header=True, header_style="bold cyan", box=ROUNDED)
776
+ table.add_column("Category", style="cyan", no_wrap=True)
777
+
778
+ # Add kit columns
779
+ for kit_key in kit_stats.keys():
780
+ table.add_column(format_kit_name(kit_key), justify="right")
781
+
782
+ # Add Total column if multiple kits
783
+ show_total = len(kit_stats) > 1
784
+ if show_total:
785
+ table.add_column("Total", justify="right")
786
+
787
+ # Add rows (only if totals > 0)
788
+ for cat in ["files", "directories"]:
789
+ values = [kit_stats[kit_key].get(cat, 0) for kit_key in kit_stats.keys()]
790
+ if sum(values) > 0:
791
+ styled_values = [style_value(v, not is_removal, has_modified_files) for v in values]
792
+ row = [cat.capitalize()] + styled_values
793
+ if show_total:
794
+ row.append(style_value(sum(values), not is_removal, has_modified_files))
795
+ table.add_row(*row)
406
796
 
407
- console.print("\n[bold cyan]Next steps:[/bold cyan]")
408
- console.print(" 1. Run: /orient (in your AI assistant)")
409
- console.print(f" 2. Check: {MARKER_PROJECT_KIT} or .github/prompts/orient.prompt.md")
410
- console.print(f" 3. Validate: {APP_NAME} validate --here")
797
+ console.print(table)
798
+ console.print()
411
799
 
800
+ def _display_installation_summary(result: dict, verbose: bool = False):
801
+ """Display kit addition summary.
412
802
 
413
- def _display_validation_results(result: dict):
414
- """Display validation results."""
415
- for check_name, check_result in result.get("checks", {}).items():
416
- status = "[OK]" if check_result["passed"] else "[X]"
417
- color = "green" if check_result["passed"] else "red"
418
- console.print(f"[{color}]{status}[/{color}] {check_name}")
803
+ Args:
804
+ result: Install result dict with 'installed' and 'skipped' lists
805
+ verbose: If True, show full file list; if False, show only count
806
+ """
807
+ installed = result.get("installed", [])
808
+ skipped = result.get("skipped", [])
809
+
810
+ if verbose and installed:
811
+ console.print("[bold]\nInstalled files:[/bold]")
812
+ for item in installed:
813
+ # Normalize to backslashes for Windows display
814
+ display_path = str(item).replace("/", "\\")
815
+ console.print(f" [green]+[/green] {display_path}")
816
+
817
+ if verbose and skipped:
818
+ console.print("\n[bold]Skipped (already exists):[/bold]")
819
+ for item in skipped:
820
+ # Normalize to backslashes for Windows display
821
+ display_path = str(item).replace("/", "\\")
822
+ console.print(f" [dim]-[/dim] {display_path}")
823
+
824
+ # Summary count (always show)
825
+ if not verbose:
826
+ if installed:
827
+ console.print(f"\nInstalled {len(installed)} files")
828
+ if skipped:
829
+ console.print(f"Skipped {len(skipped)} files (already exist)")
419
830
 
420
- if not check_result["passed"] and "message" in check_result:
421
- console.print(f" {check_result['message']}", style="dim")
831
+ console.print("\n[bold cyan]Next steps:[/bold cyan]")
832
+ console.print(f" 1. Run: /orient (in GitHub Copilot or Claude Code)")
833
+ console.print(r" 2. Check: .github\prompts\orient.prompt.md or .claude\commands\orient.md")
834
+ console.print(f" 3. Validate: {APP_NAME} validate")
835
+ console.print("\n[dim]Note: Commands are markdown prompt files that work with any compatible AI assistant.[/dim]")
836
+ console.print()
422
837
 
838
+ def _display_removal_summary(result: dict, verbose: bool = False):
839
+ """Display kit removal summary.
423
840
 
424
- @app.command(name="info", rich_help_panel=PANEL_PACKAGE_MANAGEMENT)
841
+ Args:
842
+ result: Removal result dict with 'removed' list of kit dicts
843
+ verbose: If True, show full file list; if False, show only count
844
+ """
845
+ # Flatten all removed files from all kits
846
+ all_removed = []
847
+ for kit_result in result.get("removed", []):
848
+ all_removed.extend(kit_result.get("files", []))
849
+
850
+ if verbose and all_removed:
851
+ console.print("\n[bold]Removed files:[/bold]")
852
+ for item in all_removed:
853
+ # Normalize to backslashes for Windows display
854
+ display_path = str(item).replace("/", "\\")
855
+ console.print(f" [red]-[/red] {display_path}")
856
+
857
+ # Summary count (always show)
858
+ if not verbose and all_removed:
859
+ console.print(f"\nRemoved {len(all_removed)} files")
860
+
861
+ def _display_validation_results(validation_result: dict):
862
+ """Display validation results in a user-friendly format.
863
+
864
+ Args:
865
+ validation_result: Dict with 'valid' (bool) and 'checks' (dict of kit results)
866
+ """
867
+ checks = validation_result.get("checks", {})
868
+
869
+ for kit_name, result in checks.items():
870
+ status = result.get("status", "unknown")
871
+
872
+ if status == "installed":
873
+ console.print(f"[green][OK] {kit_name}[/green]")
874
+ elif status == "not_installed":
875
+ console.print(f"[dim][-] {kit_name} (not installed)[/dim]")
876
+ elif status == "partial":
877
+ console.print(f"[yellow][!] {kit_name} (partial - some files missing)[/yellow]")
878
+ missing = result.get("missing_files", [])
879
+ if missing:
880
+ console.print(f"[dim] Missing files: {', '.join(missing[:3])}" + (" ..." if len(missing) > 3 else "") + "[/dim]")
881
+ else:
882
+ console.print(f"[red][X] {kit_name} ({status})[/red]")
883
+
884
+ def _cleanup_empty_directories(target_dir: Path):
885
+ """Clean up empty directories created by lite-kits."""
886
+ directories_to_check = [
887
+ DIR_CLAUDE_COMMANDS,
888
+ DIR_GITHUB_PROMPTS,
889
+ DIR_SPECIFY_MEMORY,
890
+ DIR_SPECIFY_SCRIPTS_BASH,
891
+ DIR_SPECIFY_SCRIPTS_POWERSHELL,
892
+ DIR_SPECIFY_TEMPLATES,
893
+ ]
894
+
895
+ cleaned = []
896
+ for dir_path in directories_to_check:
897
+ full_path = target_dir / dir_path
898
+ if full_path.exists() and full_path.is_dir():
899
+ # Check if directory is empty (no files or subdirs)
900
+ try:
901
+ if not any(full_path.iterdir()):
902
+ full_path.rmdir()
903
+ cleaned.append(dir_path)
904
+ except OSError:
905
+ # Directory not empty or permission error, skip
906
+ pass
907
+
908
+ if cleaned:
909
+ console.print(f"\nCleaned up empty directories: [dim]{', '.join(cleaned)}[/dim]")
910
+
911
+ @app.command(name="info")
425
912
  def package_info():
426
- """Show package information and installation details."""
427
- # Use __version__ from package instead of importlib.metadata
428
- console.print(f"\n[bold cyan]{APP_NAME} v{__version__}[/bold cyan]")
429
- console.print(f"[dim]{APP_DESCRIPTION}[/dim]\n")
913
+ """Show package information and available kits.
430
914
 
431
- # Package info
915
+ Displays:
916
+ - Package version and repository
917
+ - Available kits (dev, multiagent)
918
+ - Kit descriptions and commands
919
+ - Package management commands
920
+ """
921
+ # Package info (banner removed to avoid duplication with --banner flag)
922
+ console.print()
923
+ console.print("[bold]Info:[/bold]")
432
924
  info_table = Table(show_header=False, box=None, padding=(0, 2))
433
925
  info_table.add_column("Key", style="cyan")
434
926
  info_table.add_column("Value")
435
927
 
436
928
  info_table.add_row("Version", __version__)
437
- info_table.add_row("Repository", "https://github.com/tmorgan181/lite-kits")
438
- info_table.add_row("License", "MIT")
929
+ info_table.add_row("Repository", REPOSITORY_URL)
930
+ info_table.add_row("License", LICENSE)
439
931
 
440
932
  console.print(info_table)
441
933
  console.print()
442
934
 
443
935
  # Available kits
444
936
  console.print("[bold]Available Kits:[/bold]")
445
- console.print(f" • [cyan]{KIT_PROJECT}[/cyan]: {KIT_DESC_PROJECT}")
446
- console.print(f" [cyan]{KIT_GIT}[/cyan]: {KIT_DESC_GIT}")
447
- console.print(f" • [cyan]{KIT_MULTIAGENT}[/cyan]: {KIT_DESC_MULTIAGENT}")
448
- console.print()
937
+ kits_table = Table(show_header=False, box=None, padding=(0, 2))
938
+ kits_table.add_column("Kit", style="cyan")
939
+ kits_table.add_column("Description")
449
940
 
450
- # Quick start
451
- console.print("[bold]Quick Start:[/bold]")
452
- console.print(f" 1. [cyan]{APP_NAME} add --here --recommended[/cyan] # Add project + git kits")
453
- console.print(f" 2. [cyan]{APP_NAME} status --here[/cyan] # Check installation")
454
- console.print(f" 3. [cyan]/orient[/cyan] # Run in your AI assistant")
941
+ kits_table.add_row(KIT_DEV, KIT_DESC_DEV)
942
+ kits_table.add_row(KIT_MULTIAGENT, KIT_DESC_MULTIAGENT)
943
+
944
+ console.print(kits_table)
455
945
  console.print()
456
946
 
457
947
  # Package management
458
948
  console.print("[bold]Package Management:[/bold]")
459
- console.print(f" Install: [dim]uv tool install {APP_NAME}[/dim]")
460
- console.print(f" Update: [dim]uv tool install --upgrade {APP_NAME}[/dim]")
461
- console.print(f" Uninstall: [dim]uv tool uninstall {APP_NAME}[/dim]")
949
+ package_table = Table(show_header=False, box=None, padding=(0, 2))
950
+ package_table.add_column("Action", style="cyan")
951
+ package_table.add_column("Command")
952
+
953
+ package_table.add_row("Update", f"[dim]uv tool install --upgrade {APP_NAME}[/dim]")
954
+ package_table.add_row("Uninstall", f"[dim]uv tool uninstall {APP_NAME}[/dim]")
955
+
956
+ console.print(package_table)
462
957
  console.print()
463
958
 
464
-
465
- @app.command(name="uninstall", rich_help_panel=PANEL_PACKAGE_MANAGEMENT)
959
+ @app.command(name="uninstall")
466
960
  def package_uninstall():
467
961
  """Instructions for uninstalling the lite-kits package."""
468
- console.print(f"\n[bold yellow]Uninstall {APP_NAME}[/bold yellow]\n")
962
+ console.print()
963
+ console.print(f"[bold yellow]Uninstall {APP_NAME}[/bold yellow]\n")
469
964
 
470
965
  console.print("To uninstall the package, run:\n")
471
966
  console.print(f" [cyan]uv tool uninstall {APP_NAME}[/cyan]\n")
@@ -474,8 +969,39 @@ def package_uninstall():
474
969
  console.print(f" [dim]pip uninstall {APP_NAME}[/dim]\n")
475
970
 
476
971
  console.print("[bold]Note:[/bold] This will remove the package but NOT the kits you've added to projects.")
477
- console.print(f"To remove kits from a project, first run: [cyan]{APP_NAME} remove --here --all[/cyan]\n")
972
+ console.print(f"To remove kits from a project, first run: [cyan]{APP_NAME} remove --all[/cyan]\n")
973
+
974
+ @app.command(name="help")
975
+ def show_help(
976
+ ctx: typer.Context,
977
+ command_name: Optional[str] = typer.Argument(
978
+ None,
979
+ help="Command to get help for (e.g., 'add', 'remove')",
980
+ ),
981
+ ):
982
+ """Show help and available commands.
478
983
 
984
+ Usage:
985
+ lite-kits help # Show general help
986
+ lite-kits help add # Show help for 'add' command
987
+ """
988
+ if command_name:
989
+ # Show help for specific command by invoking it with --help
990
+ import sys
991
+ sys.argv = [APP_NAME, command_name, "--help"]
992
+ try:
993
+ app()
994
+ except SystemExit:
995
+ pass
996
+ else:
997
+ # Show general help
998
+ console.print(ctx.parent.get_help())
999
+ raise typer.Exit(0)
1000
+
1001
+ @app.command(name="banner", hidden=True)
1002
+ def show_banner():
1003
+ """Show the lite-kits banner (hidden easter egg command)."""
1004
+ diagonal_reveal_banner()
479
1005
 
480
1006
  if __name__ == "__main__":
481
1007
  app()