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.
- lite_kits/__init__.py +56 -4
- lite_kits/cli.py +696 -185
- lite_kits/core/__init__.py +6 -0
- lite_kits/core/banner.py +1 -1
- lite_kits/core/conflict_checker.py +115 -0
- lite_kits/core/detector.py +140 -0
- lite_kits/core/installer.py +236 -351
- lite_kits/core/manifest.py +146 -146
- lite_kits/core/validator.py +146 -0
- lite_kits/kits/README.md +6 -6
- lite_kits/kits/dev/README.md +241 -241
- lite_kits/kits/dev/{claude/commands → commands/.claude}/audit.md +143 -143
- lite_kits/kits/dev/{claude/commands → commands/.claude}/cleanup.md +2 -2
- lite_kits/kits/{git/claude/commands → dev/commands/.claude}/commit.md +2 -2
- lite_kits/kits/{project/claude/commands → dev/commands/.claude}/orient.md +3 -4
- lite_kits/kits/{git/claude/commands → dev/commands/.claude}/pr.md +1 -1
- lite_kits/kits/{git/claude/commands → dev/commands/.claude}/review.md +202 -202
- lite_kits/kits/{project/claude/commands → dev/commands/.claude}/stats.md +162 -162
- lite_kits/kits/{project/github/prompts → dev/commands/.github}/audit.prompt.md +143 -143
- lite_kits/kits/{git/github/prompts → dev/commands/.github}/cleanup.prompt.md +2 -2
- lite_kits/kits/{git/github/prompts → dev/commands/.github}/commit.prompt.md +2 -2
- lite_kits/kits/dev/{github/prompts → commands/.github}/orient.prompt.md +3 -4
- lite_kits/kits/{git/github/prompts → dev/commands/.github}/pr.prompt.md +1 -1
- lite_kits/kits/{git/github/prompts → dev/commands/.github}/review.prompt.md +202 -202
- lite_kits/kits/dev/{github/prompts → commands/.github}/stats.prompt.md +163 -163
- lite_kits/kits/kits.yaml +497 -180
- lite_kits/kits/multiagent/README.md +6 -6
- lite_kits/kits/multiagent/{claude/commands → commands/.claude}/sync.md +331 -331
- lite_kits/kits/multiagent/{github/prompts → commands/.github}/sync.prompt.md +73 -69
- lite_kits/kits/multiagent/memory/git-worktrees-protocol.md +370 -370
- lite_kits/kits/multiagent/memory/parallel-work-protocol.md +536 -536
- lite_kits/kits/multiagent/memory/pr-workflow-guide.md +275 -275
- lite_kits/kits/multiagent/templates/collaboration-structure/README.md +166 -166
- lite_kits/kits/multiagent/templates/decision.md +79 -79
- lite_kits/kits/multiagent/templates/handoff.md +95 -95
- lite_kits/kits/multiagent/templates/session-log.md +68 -68
- lite_kits-0.3.1.dist-info/METADATA +259 -0
- lite_kits-0.3.1.dist-info/RECORD +41 -0
- {lite_kits-0.1.1.dist-info → lite_kits-0.3.1.dist-info}/licenses/LICENSE +21 -21
- lite_kits/kits/dev/claude/commands/commit.md +0 -612
- lite_kits/kits/dev/claude/commands/orient.md +0 -146
- lite_kits/kits/dev/claude/commands/pr.md +0 -593
- lite_kits/kits/dev/claude/commands/review.md +0 -202
- lite_kits/kits/dev/claude/commands/stats.md +0 -162
- lite_kits/kits/dev/github/prompts/audit.prompt.md +0 -143
- lite_kits/kits/dev/github/prompts/cleanup.prompt.md +0 -382
- lite_kits/kits/dev/github/prompts/commit.prompt.md +0 -591
- lite_kits/kits/dev/github/prompts/pr.prompt.md +0 -603
- lite_kits/kits/dev/github/prompts/review.prompt.md +0 -202
- lite_kits/kits/git/README.md +0 -365
- lite_kits/kits/git/claude/commands/cleanup.md +0 -361
- lite_kits/kits/git/scripts/bash/get-git-context.sh +0 -208
- lite_kits/kits/git/scripts/powershell/Get-GitContext.ps1 +0 -242
- lite_kits/kits/project/README.md +0 -228
- lite_kits/kits/project/claude/commands/audit.md +0 -143
- lite_kits/kits/project/claude/commands/review.md +0 -112
- lite_kits/kits/project/github/prompts/orient.prompt.md +0 -150
- lite_kits/kits/project/github/prompts/review.prompt.md +0 -112
- lite_kits/kits/project/github/prompts/stats.prompt.md +0 -163
- lite_kits-0.1.1.dist-info/METADATA +0 -447
- lite_kits-0.1.1.dist-info/RECORD +0 -58
- {lite_kits-0.1.1.dist-info → lite_kits-0.3.1.dist-info}/WHEEL +0 -0
- {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
|
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
|
62
|
-
console.print(f" [cyan]2. {APP_NAME} status[/cyan]
|
63
|
-
console.print(f" [cyan]3. {APP_NAME}
|
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
|
-
|
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}
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
177
|
+
all_kits: bool = typer.Option(
|
157
178
|
False,
|
158
|
-
"--
|
159
|
-
help=
|
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
|
-
|
198
|
+
force: bool = typer.Option(
|
162
199
|
False,
|
163
|
-
"--
|
164
|
-
help="
|
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
|
177
|
-
kits =
|
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
|
222
|
+
# else: kits=None will use default from manifest
|
181
223
|
|
182
224
|
try:
|
183
|
-
installer = Installer(
|
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
|
-
|
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
|
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
|
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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
-
|
218
|
-
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
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
|
-
|
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
|
252
|
-
lite-kits remove --kit
|
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 --
|
267
|
-
console.print(f" {APP_NAME} remove --
|
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
|
-
#
|
277
|
-
if
|
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
|
-
#
|
282
|
-
|
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
|
-
|
286
|
-
|
287
|
-
|
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
|
291
|
-
|
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
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
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
|
-
-
|
317
|
-
-
|
318
|
-
-
|
319
|
-
-
|
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
|
-
|
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
|
-
|
447
|
+
print_spec_kit_error()
|
334
448
|
raise typer.Exit(1)
|
335
449
|
|
336
450
|
# Check if any kits are installed
|
337
|
-
|
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
|
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
|
-
|
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
|
478
|
+
"""Show enhancement kit installation status.
|
361
479
|
|
362
480
|
Displays:
|
363
|
-
-
|
364
|
-
-
|
365
|
-
-
|
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
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
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
|
397
|
-
"""
|
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
|
-
|
403
|
-
|
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
|
-
|
407
|
-
|
408
|
-
|
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
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
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
|
418
|
-
console.print(
|
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
|
422
|
-
"""Display
|
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
|
-
|
429
|
-
|
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
|
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
|
-
|
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",
|
446
|
-
info_table.add_row("License",
|
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(
|
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(
|
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)."""
|