lite-kits 0.1.1__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 (63) hide show
  1. lite_kits/__init__.py +56 -4
  2. lite_kits/cli.py +696 -185
  3. lite_kits/core/__init__.py +6 -0
  4. lite_kits/core/banner.py +1 -1
  5. lite_kits/core/conflict_checker.py +115 -0
  6. lite_kits/core/detector.py +140 -0
  7. lite_kits/core/installer.py +236 -351
  8. lite_kits/core/manifest.py +146 -146
  9. lite_kits/core/validator.py +146 -0
  10. lite_kits/kits/README.md +6 -6
  11. lite_kits/kits/dev/README.md +241 -241
  12. lite_kits/kits/dev/{claude/commands → commands/.claude}/audit.md +143 -143
  13. lite_kits/kits/dev/{claude/commands → 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 +3 -4
  16. lite_kits/kits/{git/claude/commands → dev/commands/.claude}/pr.md +1 -1
  17. lite_kits/kits/{git/claude/commands → dev/commands/.claude}/review.md +202 -202
  18. lite_kits/kits/{project/claude/commands → dev/commands/.claude}/stats.md +162 -162
  19. lite_kits/kits/{project/github/prompts → dev/commands/.github}/audit.prompt.md +143 -143
  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/dev/{github/prompts → commands/.github}/orient.prompt.md +3 -4
  23. lite_kits/kits/{git/github/prompts → dev/commands/.github}/pr.prompt.md +1 -1
  24. lite_kits/kits/{git/github/prompts → dev/commands/.github}/review.prompt.md +202 -202
  25. lite_kits/kits/dev/{github/prompts → commands/.github}/stats.prompt.md +163 -163
  26. lite_kits/kits/kits.yaml +497 -180
  27. lite_kits/kits/multiagent/README.md +6 -6
  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 -275
  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.1.dist-info → lite_kits-0.3.1.dist-info}/licenses/LICENSE +21 -21
  40. lite_kits/kits/dev/claude/commands/commit.md +0 -612
  41. lite_kits/kits/dev/claude/commands/orient.md +0 -146
  42. lite_kits/kits/dev/claude/commands/pr.md +0 -593
  43. lite_kits/kits/dev/claude/commands/review.md +0 -202
  44. lite_kits/kits/dev/claude/commands/stats.md +0 -162
  45. lite_kits/kits/dev/github/prompts/audit.prompt.md +0 -143
  46. lite_kits/kits/dev/github/prompts/cleanup.prompt.md +0 -382
  47. lite_kits/kits/dev/github/prompts/commit.prompt.md +0 -591
  48. lite_kits/kits/dev/github/prompts/pr.prompt.md +0 -603
  49. lite_kits/kits/dev/github/prompts/review.prompt.md +0 -202
  50. lite_kits/kits/git/README.md +0 -365
  51. lite_kits/kits/git/claude/commands/cleanup.md +0 -361
  52. lite_kits/kits/git/scripts/bash/get-git-context.sh +0 -208
  53. lite_kits/kits/git/scripts/powershell/Get-GitContext.ps1 +0 -242
  54. lite_kits/kits/project/README.md +0 -228
  55. lite_kits/kits/project/claude/commands/audit.md +0 -143
  56. lite_kits/kits/project/claude/commands/review.md +0 -112
  57. lite_kits/kits/project/github/prompts/orient.prompt.md +0 -150
  58. lite_kits/kits/project/github/prompts/review.prompt.md +0 -112
  59. lite_kits/kits/project/github/prompts/stats.prompt.md +0 -163
  60. lite_kits-0.1.1.dist-info/METADATA +0 -447
  61. lite_kits-0.1.1.dist-info/RECORD +0 -58
  62. {lite_kits-0.1.1.dist-info → lite_kits-0.3.1.dist-info}/WHEEL +0 -0
  63. {lite_kits-0.1.1.dist-info → lite_kits-0.3.1.dist-info}/entry_points.txt +0 -0
lite_kits/cli.py CHANGED
@@ -14,31 +14,28 @@ 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__
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
+ )
18
37
  from .core import diagonal_reveal_banner, show_loading_spinner, show_static_banner, Installer
19
38
 
20
- # Constants
21
- APP_NAME = "lite-kits"
22
- APP_DESCRIPTION = "Lightweight enhancement kits for spec-driven development."
23
-
24
- # Kit names
25
- KIT_DEV = "dev"
26
- KIT_MULTIAGENT = "multiagent"
27
- KITS_ALL = [KIT_DEV, KIT_MULTIAGENT]
28
- KITS_RECOMMENDED = [KIT_DEV] # dev-kit is the default, multiagent is optional
29
-
30
- # Kit descriptions for help
31
- KIT_DESC_DEV = "Solo development essentials: /orient, /commit, /pr, /review, /cleanup, /audit, /stats"
32
- KIT_DESC_MULTIAGENT = "Multi-agent coordination: /sync, collaboration dirs, memory guides (optional)"
33
-
34
- # Marker files for kit detection
35
- MARKER_DEV_KIT = ".claude/commands/orient.md" # If orient exists, dev-kit is installed
36
- MARKER_MULTIAGENT_KIT = ".specify/memory/pr-workflow-guide.md"
37
-
38
- # Error messages
39
- ERROR_NOT_SPEC_KIT = "does not appear to be a spec-kit project!"
40
- ERROR_SPEC_KIT_HINT = "Looking for one of: .specify/, .claude/, or .github/prompts/"
41
-
42
39
  app = typer.Typer(
43
40
  name=APP_NAME,
44
41
  help=APP_DESCRIPTION, # Restore original description for --help
@@ -58,9 +55,30 @@ def print_version_info():
58
55
 
59
56
  def print_quick_start():
60
57
  console.print("[bold]Quick Start:[/bold]")
61
- console.print(f" [cyan]1. {APP_NAME} add --recommended[/cyan] # Add project + git kits")
62
- console.print(f" [cyan]2. {APP_NAME} status[/cyan] # Check installation")
63
- console.print(f" [cyan]3. {APP_NAME} info[/cyan] # Package details\n")
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()
64
82
 
65
83
  def print_kit_info(target_dir: Path, is_spec_kit: bool, installed_kits: list):
66
84
  """Print kit installation info."""
@@ -69,19 +87,13 @@ def print_kit_info(target_dir: Path, is_spec_kit: bool, installed_kits: list):
69
87
  console.print(f"[bold green][OK] Spec-kit project detected in {target_dir}.[/bold green]\n")
70
88
  if installed_kits:
71
89
  console.print("Installed kits:", style="bold")
72
- kit_icons = {
73
- "project": "🎯",
74
- "git": "🔧",
75
- "multiagent": "🤝"
76
- }
77
90
  for kit in installed_kits:
78
- icon = kit_icons.get(kit, "📦")
79
- console.print(f" {icon} {kit}-kit", style="green")
91
+ console.print(f" [green]+[/green] {kit}-kit")
80
92
  else:
81
93
  console.print("No kits installed.", style="dim yellow")
82
94
  else:
83
- console.print(f"[bold red][X] {target_dir} {ERROR_NOT_SPEC_KIT}[/bold red]")
84
- console.print(f"{ERROR_SPEC_KIT_HINT}", style="dim")
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")
85
97
  console.print()
86
98
 
87
99
  def version_callback(value: bool):
@@ -89,14 +101,7 @@ def version_callback(value: bool):
89
101
  if value:
90
102
  console.print()
91
103
  print_version_info()
92
- raise typer.Exit()
93
-
94
- def banner_callback(value: bool):
95
- """Show banner + hint and exit."""
96
- if value:
97
- diagonal_reveal_banner()
98
- print_help_hint()
99
- print_quick_start()
104
+ console.print()
100
105
  raise typer.Exit()
101
106
 
102
107
  @app.callback(invoke_without_command=True)
@@ -105,7 +110,7 @@ def main(
105
110
  version: Optional[bool] = typer.Option(
106
111
  None,
107
112
  "--version",
108
- "-V",
113
+ "-V",
109
114
  help="Display the lite-kits version",
110
115
  callback=version_callback,
111
116
  is_eager=True,
@@ -113,9 +118,7 @@ def main(
113
118
  banner: Optional[bool] = typer.Option(
114
119
  None,
115
120
  "--banner",
116
- help="Show the lite-kits banner",
117
- callback=banner_callback,
118
- is_eager=True,
121
+ help="Show the animated banner (can be combined with other commands)",
119
122
  ),
120
123
  quiet: Optional[bool] = typer.Option(
121
124
  None,
@@ -125,7 +128,7 @@ def main(
125
128
  ),
126
129
  verbose: Optional[bool] = typer.Option(
127
130
  None,
128
- "--verbose",
131
+ "--verbose",
129
132
  "-v",
130
133
  help="Use verbose output",
131
134
  ),
@@ -139,13 +142,31 @@ def main(
139
142
  if directory:
140
143
  import os
141
144
  os.chdir(directory)
142
-
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
+
143
158
  # Show banner + hint and quick-start when no command is given
144
159
  if ctx.invoked_subcommand is None:
145
- show_static_banner()
160
+ if not banner: # Only show static banner if animated not already shown
161
+ show_static_banner()
146
162
  print_help_hint()
147
163
  print_quick_start()
148
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
+
149
170
  @app.command(name="add")
150
171
  def add_kits(
151
172
  kit: Optional[str] = typer.Option(
@@ -153,84 +174,142 @@ def add_kits(
153
174
  "--kit",
154
175
  help=f"Comma-separated list of kits to add: {','.join(KITS_ALL)}",
155
176
  ),
156
- recommended: bool = typer.Option(
177
+ all_kits: bool = typer.Option(
157
178
  False,
158
- "--recommended",
159
- help=f"Add recommended kits: {' + '.join(KITS_RECOMMENDED)}",
179
+ "--all",
180
+ help="Add all kits",
181
+ ),
182
+ agent: Optional[str] = typer.Option(
183
+ None,
184
+ "--agent",
185
+ help="Explicit agent preference (claude, copilot, etc.)",
186
+ ),
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",
160
197
  ),
161
- dry_run: bool = typer.Option(
198
+ force: bool = typer.Option(
162
199
  False,
163
- "--dry-run",
164
- help="Preview changes without applying them",
200
+ "--force",
201
+ help="Skip preview and confirmations, overwrite existing files",
165
202
  ),
166
203
  target: Optional[Path] = typer.Argument(
167
204
  None,
168
205
  help="Target directory (defaults to current directory)",
169
206
  ),
170
207
  ):
171
- """Add enhancement kits to a spec-kit project."""
208
+ """Add enhancement kits to a spec-kit project.
209
+
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
+ """
172
214
  target_dir = Path.cwd() if target is None else target
173
215
 
174
216
  # Determine which kits to install
175
217
  kits = None
176
- if recommended:
177
- kits = KITS_RECOMMENDED
218
+ if all_kits:
219
+ kits = KITS_ALL
178
220
  elif kit:
179
221
  kits = [k.strip() for k in kit.split(',')]
180
- # else: kits=None will use default [KIT_PROJECT] in Installer
222
+ # else: kits=None will use default from manifest
181
223
 
182
224
  try:
183
- 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
+ )
184
232
  except ValueError as e:
233
+ console.print()
185
234
  console.print(f"[red]Error:[/red] {e}", style="bold")
235
+ console.print()
186
236
  raise typer.Exit(1)
187
237
 
188
238
  # Validate target is a spec-kit project
189
239
  if not installer.is_spec_kit_project():
190
- console.print(
191
- f"[red]Error:[/red] {target_dir} {ERROR_NOT_SPEC_KIT}",
192
- style="bold",
193
- )
194
- console.print(
195
- f"\n{ERROR_SPEC_KIT_HINT}",
196
- style="dim",
197
- )
240
+ print_spec_kit_error()
198
241
  raise typer.Exit(1)
199
242
 
200
- # Check if already installed
201
- 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()
202
249
  console.print(
203
250
  "[yellow]Warning:[/yellow] Enhancement kits appear to be already installed",
204
251
  style="bold",
205
252
  )
206
- 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()
207
283
  raise typer.Exit(0)
208
284
 
209
- # Preview or install
210
- if dry_run:
211
- console.print("\n[bold cyan]Dry run - no changes will be made[/bold cyan]\n")
212
- changes = installer.preview_installation()
213
- _display_changes(changes)
214
- else:
215
- console.print(f"\n[bold green]Adding enhancement kits to {target_dir}[/bold green]\n")
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
+ )
216
294
 
217
- show_loading_spinner("Adding kits...")
218
- result = installer.install()
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()
219
299
 
220
- if result["success"]:
221
- console.print("\n[bold green][OK] Kits added successfully![/bold green]\n")
222
- _display_installation_summary(result)
223
- # Show static banner after successful install
224
- show_static_banner()
225
- else:
226
- console.print(f"\n[bold red][X] Failed to add kits:[/bold red] {result['error']}\n")
227
- raise typer.Exit(1)
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)
228
307
 
229
308
  @app.command()
230
309
  def remove(
231
310
  kit: Optional[str] = typer.Option(
232
311
  None,
233
- "--kit",
312
+ "--kit",
234
313
  help=f"Comma-separated list of kits to remove: {','.join(KITS_ALL)}",
235
314
  ),
236
315
  all_kits: bool = typer.Option(
@@ -238,6 +317,17 @@ def remove(
238
317
  "--all",
239
318
  help="Remove all kits",
240
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
+ ),
241
331
  target: Optional[Path] = typer.Argument(
242
332
  None,
243
333
  help="Target directory (defaults to current directory)",
@@ -245,12 +335,16 @@ def remove(
245
335
  ):
246
336
  """Remove enhancement kits from a spec-kit project.
247
337
 
248
- 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.
249
342
 
250
343
  Examples:
251
- lite-kits remove --kit git # Remove git-kit only
252
- lite-kits remove --kit project,git # Remove specific kits
344
+ lite-kits remove --kit dev # Remove dev-kit
345
+ lite-kits remove --kit dev,multiagent # Remove multiple kits
253
346
  lite-kits remove --all # Remove all kits
347
+ lite-kits remove --all --force # Remove all kits without confirmation
254
348
  """
255
349
  target_dir = Path.cwd() if target is None else target
256
350
 
@@ -261,46 +355,67 @@ def remove(
261
355
  elif kit:
262
356
  kits = [k.strip() for k in kit.split(',')]
263
357
  else:
358
+ console.print()
264
359
  console.print("[yellow]Error:[/yellow] Specify --kit or --all", style="bold")
265
360
  console.print("\nExamples:", style="dim")
266
- console.print(f" {APP_NAME} remove --here --kit {KIT_GIT}", style="dim")
267
- 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()
268
364
  raise typer.Exit(1)
269
365
 
270
366
  try:
271
367
  installer = Installer(target_dir, kits=kits)
272
368
  except ValueError as e:
369
+ console.print()
273
370
  console.print(f"[red]Error:[/red] {e}", style="bold")
371
+ console.print()
274
372
  raise typer.Exit(1)
275
373
 
276
- # Check if kits are installed
277
- 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()
278
378
  console.print("[yellow]Warning:[/yellow] No kits detected to remove", style="bold")
379
+ console.print()
279
380
  raise typer.Exit(0)
280
381
 
281
- # Confirm removal
282
- console.print(f"\n[bold yellow]Remove kits from {target_dir}[/bold yellow]")
283
- console.print(f"Kits to remove: {', '.join(kits)}\n")
382
+ # Update installer with filtered list
383
+ installer = Installer(target_dir, kits=installed_kits)
284
384
 
285
- if not typer.confirm("Continue with removal?"):
286
- console.print("Cancelled")
287
- 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)
288
405
 
289
406
  # Remove kits
290
- console.print("\n[bold]Removing kits...[/bold]\n")
291
- with console.status("[bold yellow]Removing..."):
292
- result = installer.remove()
407
+ console.print(f"\n[bold yellow]Removing files...[/bold yellow]")
408
+ result = installer.remove()
293
409
 
294
410
  if result["success"]:
295
- console.print("[bold green]Removal complete![/bold green]\n")
296
- if result["removed"]:
297
- console.print("[bold]Removed:[/bold]")
298
- for item in result["removed"]:
299
- console.print(f" - {item}")
300
- else:
301
- 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")
302
416
  else:
303
- 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()
304
419
  raise typer.Exit(1)
305
420
 
306
421
  @app.command()
@@ -310,37 +425,40 @@ def validate(
310
425
  help="Target directory (defaults to current directory)",
311
426
  ),
312
427
  ):
313
- """Validate enhancement kit installation.
428
+ """Validate enhancement kit installation integrity.
314
429
 
315
430
  Checks:
316
- - Kit files are present and correctly installed
317
- - Collaboration directory structure (if multiagent-kit installed)
318
- - Required files present
319
- - 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)
320
435
 
321
436
  Example:
322
- lite-kits validate
437
+ lite-kits validate # Validate current directory
438
+ lite-kits validate path/to/dir # Validate specific directory
323
439
  """
324
440
  target_dir = Path.cwd() if target is None else target
325
441
 
326
442
  # For validation, we don't know which kits are installed yet, so check for all
327
443
  installer = Installer(target_dir, kits=KITS_ALL)
328
444
 
329
- console.print(f"\n[bold cyan]Validating {target_dir}[/bold cyan]\n")
330
-
331
- # Check if it's a spec-kit project
445
+ # Check if it's a spec-kit project first
332
446
  if not installer.is_spec_kit_project():
333
- console.print("[red][X] Not a spec-kit project[/red]")
447
+ print_spec_kit_error()
334
448
  raise typer.Exit(1)
335
449
 
336
450
  # Check if any kits are installed
337
- 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()
338
454
  console.print("[yellow]⚠ No enhancement kits installed[/yellow]")
339
- console.print(f" Run: {APP_NAME} add --here --recommended", style="dim")
455
+ console.print(f" Run: {APP_NAME} add", style="dim")
456
+ console.print()
340
457
  raise typer.Exit(1)
341
458
 
342
459
  # Validate structure
343
- validation_result = show_loading_spinner("Validating...", installer.validate)
460
+ console.print(f"\n[bold cyan]Validating {target_dir}[/bold cyan]\n")
461
+ validation_result = installer.validate()
344
462
  _display_validation_results(validation_result)
345
463
 
346
464
  if validation_result["valid"]:
@@ -357,15 +475,16 @@ def status(
357
475
  help="Target directory (defaults to current directory)",
358
476
  ),
359
477
  ):
360
- """Show enhancement kit installation status for the project.
478
+ """Show enhancement kit installation status.
361
479
 
362
480
  Displays:
363
- - Spec-kit project detection
364
- - Installed kits
365
- - Installation health
481
+ - Whether directory is a spec-kit project
482
+ - Which kits are installed (dev, multiagent)
483
+ - Quick summary of installation state
366
484
 
367
485
  Example:
368
- lite-kits status
486
+ lite-kits status # Check current directory
487
+ lite-kits status path/to/dir # Check specific directory
369
488
  """
370
489
  target_dir = Path.cwd() if target is None else target
371
490
 
@@ -375,75 +494,440 @@ def status(
375
494
  # Basic checks
376
495
  is_spec_kit = installer.is_spec_kit_project()
377
496
 
378
- # Check individual kits
379
- project_kit_installed = (target_dir / MARKER_PROJECT_KIT).exists()
380
- git_kit_installed = (target_dir / MARKER_GIT_KIT).exists()
381
- multiagent_kit_installed = (target_dir / MARKER_MULTIAGENT_KIT).exists()
382
-
383
- # Build list of installed kits for banner
497
+ # Check individual kits using the installer's validator
384
498
  installed_kits = []
385
- if project_kit_installed:
386
- installed_kits.append("project")
387
- if git_kit_installed:
388
- installed_kits.append("git")
389
- if multiagent_kit_installed:
390
- installed_kits.append("multiagent")
391
-
392
- # Show banner + kit info
393
- show_static_banner()
499
+ for kit_name in KITS_ALL:
500
+ if installer.is_kit_installed(kit_name):
501
+ installed_kits.append(kit_name)
502
+
503
+ # Show kit info (skip banner to avoid Windows console Unicode issues)
394
504
  print_kit_info(target_dir, is_spec_kit, installed_kits)
395
505
 
396
- def _display_changes(changes: dict):
397
- """Display preview of changes."""
398
- console.print("[bold]Files to be created:[/bold]")
399
- for file in changes.get("new_files", []):
400
- console.print(f" [green]+[/green] {file}")
506
+ def _normalize_preview_for_display(preview: dict, operation: str = "install") -> dict:
507
+ """Normalize preview data to standard format for display.
401
508
 
402
- console.print("\n[bold]Files to be modified:[/bold]")
403
- for file in changes.get("modified_files", []):
404
- console.print(f" [yellow]~[/yellow] {file}")
509
+ Converts both installation and removal previews to a unified format
510
+ that _display_changes can handle.
405
511
 
406
- console.print("\n[bold]Directories to be created:[/bold]")
407
- for dir in changes.get("new_directories", []):
408
- console.print(f" [blue]+[/blue] {dir}")
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
409
515
 
410
- def _display_installation_summary(result: dict):
411
- """Display kit addition summary."""
412
- console.print("[bold]Added:[/bold]")
413
- for item in result.get("installed", []):
414
- console.print(f" [OK] {item}")
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}")
545
+
546
+ def _display_changes(changes: dict, verbose: bool = False):
547
+ """Display preview of changes.
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
+ )
570
+
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)
796
+
797
+ console.print(table)
798
+ console.print()
799
+
800
+ def _display_installation_summary(result: dict, verbose: bool = False):
801
+ """Display kit addition summary.
802
+
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)")
415
830
 
416
831
  console.print("\n[bold cyan]Next steps:[/bold cyan]")
417
- console.print(f" 1. Run: /orient (in your AI assistant)")
418
- console.print(f" 2. Check: {MARKER_PROJECT_KIT} or .github/prompts/orient.prompt.md")
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")
419
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()
420
837
 
421
- def _display_validation_results(result: dict):
422
- """Display validation results."""
423
- for check_name, check_result in result.get("checks", {}).items():
424
- status = "[OK]" if check_result["passed"] else "[X]"
425
- color = "green" if check_result["passed"] else "red"
426
- console.print(f"[{color}]{status}[/{color}] {check_name}")
838
+ def _display_removal_summary(result: dict, verbose: bool = False):
839
+ """Display kit removal summary.
427
840
 
428
- if not check_result["passed"] and "message" in check_result:
429
- console.print(f" {check_result['message']}", style="dim")
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]")
430
910
 
431
911
  @app.command(name="info")
432
912
  def package_info():
433
- """Show package information and installation details."""
434
- # Show the static banner for visual appeal
435
- show_static_banner()
436
- console.print()
913
+ """Show package information and available kits.
437
914
 
438
- # 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()
439
923
  console.print("[bold]Info:[/bold]")
440
924
  info_table = Table(show_header=False, box=None, padding=(0, 2))
441
925
  info_table.add_column("Key", style="cyan")
442
926
  info_table.add_column("Value")
443
927
 
444
928
  info_table.add_row("Version", __version__)
445
- info_table.add_row("Repository", "https://github.com/tmorgan181/lite-kits")
446
- info_table.add_row("License", "MIT")
929
+ info_table.add_row("Repository", REPOSITORY_URL)
930
+ info_table.add_row("License", LICENSE)
447
931
 
448
932
  console.print(info_table)
449
933
  console.print()
@@ -453,11 +937,10 @@ def package_info():
453
937
  kits_table = Table(show_header=False, box=None, padding=(0, 2))
454
938
  kits_table.add_column("Kit", style="cyan")
455
939
  kits_table.add_column("Description")
456
-
457
- kits_table.add_row(KIT_PROJECT, KIT_DESC_PROJECT)
458
- kits_table.add_row(KIT_GIT, KIT_DESC_GIT)
940
+
941
+ kits_table.add_row(KIT_DEV, KIT_DESC_DEV)
459
942
  kits_table.add_row(KIT_MULTIAGENT, KIT_DESC_MULTIAGENT)
460
-
943
+
461
944
  console.print(kits_table)
462
945
  console.print()
463
946
 
@@ -476,7 +959,8 @@ def package_info():
476
959
  @app.command(name="uninstall")
477
960
  def package_uninstall():
478
961
  """Instructions for uninstalling the lite-kits package."""
479
- 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")
480
964
 
481
965
  console.print("To uninstall the package, run:\n")
482
966
  console.print(f" [cyan]uv tool uninstall {APP_NAME}[/cyan]\n")
@@ -487,6 +971,33 @@ def package_uninstall():
487
971
  console.print("[bold]Note:[/bold] This will remove the package but NOT the kits you've added to projects.")
488
972
  console.print(f"To remove kits from a project, first run: [cyan]{APP_NAME} remove --all[/cyan]\n")
489
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.
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
+
490
1001
  @app.command(name="banner", hidden=True)
491
1002
  def show_banner():
492
1003
  """Show the lite-kits banner (hidden easter egg command)."""