cl-preset 0.1.0__py3-none-any.whl → 0.2.0__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.
- cl_preset/cli.py +60 -42
- cl_preset/interactive.py +347 -0
- {cl_preset-0.1.0.dist-info → cl_preset-0.2.0.dist-info}/METADATA +22 -1
- {cl_preset-0.1.0.dist-info → cl_preset-0.2.0.dist-info}/RECORD +7 -6
- {cl_preset-0.1.0.dist-info → cl_preset-0.2.0.dist-info}/WHEEL +0 -0
- {cl_preset-0.1.0.dist-info → cl_preset-0.2.0.dist-info}/entry_points.txt +0 -0
- {cl_preset-0.1.0.dist-info → cl_preset-0.2.0.dist-info}/licenses/LICENSE +0 -0
cl_preset/cli.py
CHANGED
|
@@ -10,17 +10,25 @@ from rich.console import Console
|
|
|
10
10
|
from rich.panel import Panel
|
|
11
11
|
|
|
12
12
|
from cl_preset.git_strategy import GitStrategyManager
|
|
13
|
+
from cl_preset.interactive import InteractiveCLI
|
|
13
14
|
from cl_preset.manager import PresetManager
|
|
14
15
|
from cl_preset.models import PresetMetadata, PresetScope, PresetType
|
|
15
16
|
|
|
16
17
|
console = Console()
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
@click.group()
|
|
20
|
+
@click.group(invoke_without_command=True)
|
|
20
21
|
@click.version_option(version="0.1.0", prog_name="cl-preset")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
@click.pass_context
|
|
23
|
+
def main(ctx: click.Context) -> None:
|
|
24
|
+
"""cl-preset: Manage Claude Code configuration presets and strategies.
|
|
25
|
+
|
|
26
|
+
Running without a command starts the interactive strategy selector.
|
|
27
|
+
"""
|
|
28
|
+
# If no subcommand is provided, run interactive mode
|
|
29
|
+
if ctx.invoked_subcommand is None:
|
|
30
|
+
interactive = InteractiveCLI()
|
|
31
|
+
interactive.run()
|
|
24
32
|
|
|
25
33
|
|
|
26
34
|
@main.command()
|
|
@@ -29,22 +37,22 @@ def main() -> None:
|
|
|
29
37
|
@click.option("--version", "-v", default="1.0.0", help="Version (default: 1.0.0)")
|
|
30
38
|
@click.option("--description", "-d", required=True, help="Description of the preset")
|
|
31
39
|
@click.option("--author", "-a", default="", help="Author name")
|
|
32
|
-
@click.option(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
@click.option(
|
|
41
|
+
"--type",
|
|
42
|
+
"-t",
|
|
43
|
+
type=click.Choice(["agent", "skill", "command", "strategy"]),
|
|
44
|
+
default="strategy",
|
|
45
|
+
help="Type of preset (default: strategy)",
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
"--scope",
|
|
49
|
+
"-s",
|
|
50
|
+
type=click.Choice(["global", "user", "project"]),
|
|
51
|
+
default="user",
|
|
52
|
+
help="Installation scope (default: user)",
|
|
53
|
+
)
|
|
40
54
|
def install(
|
|
41
|
-
source_path: Path,
|
|
42
|
-
name: str,
|
|
43
|
-
version: str,
|
|
44
|
-
description: str,
|
|
45
|
-
author: str,
|
|
46
|
-
type: str,
|
|
47
|
-
scope: str
|
|
55
|
+
source_path: Path, name: str, version: str, description: str, author: str, type: str, scope: str
|
|
48
56
|
) -> None:
|
|
49
57
|
"""Install a preset from a source directory.
|
|
50
58
|
|
|
@@ -60,13 +68,11 @@ def install(
|
|
|
60
68
|
version=version,
|
|
61
69
|
description=description,
|
|
62
70
|
author=author,
|
|
63
|
-
preset_type=PresetType(type)
|
|
71
|
+
preset_type=PresetType(type),
|
|
64
72
|
)
|
|
65
73
|
|
|
66
74
|
preset = manager.create_from_directory(
|
|
67
|
-
source_path=source_path,
|
|
68
|
-
metadata=metadata,
|
|
69
|
-
scope=PresetScope(scope)
|
|
75
|
+
source_path=source_path, metadata=metadata, scope=PresetScope(scope)
|
|
70
76
|
)
|
|
71
77
|
|
|
72
78
|
manager.install(preset)
|
|
@@ -74,9 +80,9 @@ def install(
|
|
|
74
80
|
|
|
75
81
|
@main.command()
|
|
76
82
|
@click.argument("name")
|
|
77
|
-
@click.option(
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
@click.option(
|
|
84
|
+
"--scope", "-s", type=click.Choice(["global", "user", "project"]), help="Filter by scope"
|
|
85
|
+
)
|
|
80
86
|
def uninstall(name: str, scope: str | None) -> None:
|
|
81
87
|
"""Uninstall a preset by name.
|
|
82
88
|
|
|
@@ -91,9 +97,9 @@ def uninstall(name: str, scope: str | None) -> None:
|
|
|
91
97
|
|
|
92
98
|
|
|
93
99
|
@main.command()
|
|
94
|
-
@click.option(
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
@click.option(
|
|
101
|
+
"--scope", "-s", type=click.Choice(["global", "user", "project"]), help="Filter by scope"
|
|
102
|
+
)
|
|
97
103
|
@click.option("--json", "json_output", is_flag=True, help="Output as JSON")
|
|
98
104
|
def list_cmd(scope: str | None, json_output: bool) -> None:
|
|
99
105
|
"""List installed presets.
|
|
@@ -114,9 +120,13 @@ def list_cmd(scope: str | None, json_output: bool) -> None:
|
|
|
114
120
|
|
|
115
121
|
@main.command()
|
|
116
122
|
@click.argument("name")
|
|
117
|
-
@click.option(
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
@click.option(
|
|
124
|
+
"--output",
|
|
125
|
+
"-o",
|
|
126
|
+
type=click.Path(path_type=Path),
|
|
127
|
+
default=None,
|
|
128
|
+
help="Output path for preset metadata",
|
|
129
|
+
)
|
|
120
130
|
def init(name: str, output: Path | None) -> None:
|
|
121
131
|
"""Initialize a new preset scaffold.
|
|
122
132
|
|
|
@@ -177,14 +187,16 @@ Add your name here
|
|
|
177
187
|
"author": "",
|
|
178
188
|
"license": "MIT",
|
|
179
189
|
"preset_type": "strategy",
|
|
180
|
-
"tags": []
|
|
190
|
+
"tags": [],
|
|
181
191
|
}
|
|
182
192
|
output.write_text(json.dumps(metadata, indent=2))
|
|
183
193
|
|
|
184
194
|
console.print(f"[green]Created preset scaffold at {preset_dir}[/green]")
|
|
185
195
|
console.print(f"[cyan]Metadata written to {output}[/cyan]")
|
|
186
196
|
console.print("\nEdit the metadata and preset files, then install with:")
|
|
187
|
-
console.print(
|
|
197
|
+
console.print(
|
|
198
|
+
f" cl-preset install {preset_dir} --name {name} --description 'Your description'"
|
|
199
|
+
)
|
|
188
200
|
|
|
189
201
|
|
|
190
202
|
@main.command()
|
|
@@ -236,6 +248,7 @@ def info(name: str) -> None:
|
|
|
236
248
|
# Git Strategy Commands
|
|
237
249
|
# ============================================================================
|
|
238
250
|
|
|
251
|
+
|
|
239
252
|
@click.group()
|
|
240
253
|
def git() -> None:
|
|
241
254
|
"""Git strategy management commands."""
|
|
@@ -274,7 +287,8 @@ def git_info(name: str) -> None:
|
|
|
274
287
|
|
|
275
288
|
# Handle strategy_type being either a string or enum (due to use_enum_values=True)
|
|
276
289
|
strategy_type = (
|
|
277
|
-
strategy.strategy_type
|
|
290
|
+
strategy.strategy_type
|
|
291
|
+
if isinstance(strategy.strategy_type, str)
|
|
278
292
|
else strategy.strategy_type.value
|
|
279
293
|
)
|
|
280
294
|
|
|
@@ -327,8 +341,13 @@ def git_info(name: str) -> None:
|
|
|
327
341
|
|
|
328
342
|
@git.command("export")
|
|
329
343
|
@click.argument("name")
|
|
330
|
-
@click.option(
|
|
331
|
-
|
|
344
|
+
@click.option(
|
|
345
|
+
"--output",
|
|
346
|
+
"-o",
|
|
347
|
+
type=click.Path(path_type=Path),
|
|
348
|
+
default=None,
|
|
349
|
+
help="Output path (default: ./<name>.yaml)",
|
|
350
|
+
)
|
|
332
351
|
def git_export(name: str, output: Path | None) -> None:
|
|
333
352
|
"""Export a git strategy to YAML file.
|
|
334
353
|
|
|
@@ -395,13 +414,12 @@ def git_apply(name: str, dry_run: bool) -> None:
|
|
|
395
414
|
# Check if we're in a git repository
|
|
396
415
|
try:
|
|
397
416
|
result = subprocess.run(
|
|
398
|
-
["git", "rev-parse", "--git-dir"],
|
|
399
|
-
capture_output=True,
|
|
400
|
-
text=True,
|
|
401
|
-
cwd=Path.cwd()
|
|
417
|
+
["git", "rev-parse", "--git-dir"], capture_output=True, text=True, cwd=Path.cwd()
|
|
402
418
|
)
|
|
403
419
|
if result.returncode != 0:
|
|
404
|
-
console.print(
|
|
420
|
+
console.print(
|
|
421
|
+
"[red]Not in a git repository. Initialize one first with 'git init'[/red]"
|
|
422
|
+
)
|
|
405
423
|
raise click.Abort()
|
|
406
424
|
|
|
407
425
|
# Create .moai/config/sections directory
|
cl_preset/interactive.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interactive CLI module for cl-preset package.
|
|
3
|
+
|
|
4
|
+
Provides beautiful interactive prompts for strategy selection and installation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
import questionary
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.text import Text
|
|
14
|
+
|
|
15
|
+
from cl_preset.git_strategy import GitStrategyManager
|
|
16
|
+
from cl_preset.manager import PresetManager
|
|
17
|
+
from cl_preset.models import PresetMetadata, PresetScope, PresetType
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class InteractiveCLI:
|
|
24
|
+
"""Interactive CLI for strategy selection and installation."""
|
|
25
|
+
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
"""Initialize the interactive CLI."""
|
|
28
|
+
self.console = Console()
|
|
29
|
+
self.preset_manager = PresetManager()
|
|
30
|
+
self.git_manager = GitStrategyManager()
|
|
31
|
+
|
|
32
|
+
def show_welcome(self) -> None:
|
|
33
|
+
"""Show welcome message with styled panel."""
|
|
34
|
+
welcome_text = Text()
|
|
35
|
+
welcome_text.append("Welcome to ", style="white")
|
|
36
|
+
welcome_text.append("cl-preset", style="bold cyan")
|
|
37
|
+
welcome_text.append("! ", style="white")
|
|
38
|
+
welcome_text.append("Interactive Strategy Management", style="dim")
|
|
39
|
+
|
|
40
|
+
panel = Panel(
|
|
41
|
+
welcome_text,
|
|
42
|
+
border_style="cyan",
|
|
43
|
+
padding=(1, 2),
|
|
44
|
+
)
|
|
45
|
+
self.console.print(panel)
|
|
46
|
+
self.console.print()
|
|
47
|
+
|
|
48
|
+
def get_available_strategies(self) -> list[dict]:
|
|
49
|
+
"""
|
|
50
|
+
Get all available strategies (presets and git strategies).
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
List of strategy dictionaries with name, type, and description
|
|
54
|
+
"""
|
|
55
|
+
strategies = []
|
|
56
|
+
|
|
57
|
+
# Add built-in presets from examples directory
|
|
58
|
+
examples_path = Path(__file__).parent.parent.parent / "examples" / "presets"
|
|
59
|
+
if examples_path.exists():
|
|
60
|
+
for preset_dir in examples_path.iterdir():
|
|
61
|
+
if preset_dir.is_dir():
|
|
62
|
+
readme_path = preset_dir / "README.md"
|
|
63
|
+
description = ""
|
|
64
|
+
if readme_path.exists():
|
|
65
|
+
description = readme_path.read_text()
|
|
66
|
+
# Extract first line after title
|
|
67
|
+
for line in description.split("\n")[1:10]:
|
|
68
|
+
line = line.strip()
|
|
69
|
+
if line and not line.startswith("#"):
|
|
70
|
+
description = line
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
strategies.append(
|
|
74
|
+
{
|
|
75
|
+
"name": preset_dir.name,
|
|
76
|
+
"type": "preset",
|
|
77
|
+
"description": description or f"Preset from {preset_dir.name}",
|
|
78
|
+
"path": preset_dir,
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Add git strategies
|
|
83
|
+
git_strategies = self.git_manager.list_strategies(include_builtin=True)
|
|
84
|
+
for strategy in git_strategies:
|
|
85
|
+
strategies.append(
|
|
86
|
+
{
|
|
87
|
+
"name": strategy["name"],
|
|
88
|
+
"type": "git-strategy",
|
|
89
|
+
"description": strategy["description"],
|
|
90
|
+
"source": strategy.get("source", "builtin"),
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return strategies
|
|
95
|
+
|
|
96
|
+
def select_strategy(self) -> dict | None:
|
|
97
|
+
"""
|
|
98
|
+
Show interactive strategy selection prompt.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Selected strategy dictionary or None if cancelled
|
|
102
|
+
"""
|
|
103
|
+
strategies = self.get_available_strategies()
|
|
104
|
+
|
|
105
|
+
if not strategies:
|
|
106
|
+
self.console.print("[yellow]No strategies available.[/yellow]")
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
# Format choices for questionary
|
|
110
|
+
choices = []
|
|
111
|
+
for strategy in strategies:
|
|
112
|
+
type_label = "[Preset]" if strategy["type"] == "preset" else "[Git]"
|
|
113
|
+
choice = questionary.Choice(
|
|
114
|
+
title=f"{type_label} {strategy['name']}",
|
|
115
|
+
value=strategy["name"],
|
|
116
|
+
description=strategy["description"][:70] + "..."
|
|
117
|
+
if len(strategy["description"]) > 70
|
|
118
|
+
else strategy["description"],
|
|
119
|
+
)
|
|
120
|
+
choices.append(choice)
|
|
121
|
+
|
|
122
|
+
# Add cancel option
|
|
123
|
+
choices.append(questionary.Separator())
|
|
124
|
+
choices.append(questionary.Choice(title="Cancel", value="cancel"))
|
|
125
|
+
|
|
126
|
+
selected_name = questionary.select(
|
|
127
|
+
"Select a strategy to install:",
|
|
128
|
+
choices=choices,
|
|
129
|
+
qmark=">",
|
|
130
|
+
pointer=">",
|
|
131
|
+
).ask()
|
|
132
|
+
|
|
133
|
+
if selected_name == "cancel" or selected_name is None:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
# Find the selected strategy
|
|
137
|
+
for strategy in strategies:
|
|
138
|
+
if strategy["name"] == selected_name:
|
|
139
|
+
return strategy
|
|
140
|
+
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
def select_scope(self) -> PresetScope | None:
|
|
144
|
+
"""
|
|
145
|
+
Show interactive scope selection prompt.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Selected PresetScope or None if cancelled
|
|
149
|
+
"""
|
|
150
|
+
scope_choices = [
|
|
151
|
+
questionary.Choice(
|
|
152
|
+
title="global (System-wide installation)",
|
|
153
|
+
value="global",
|
|
154
|
+
description="Install for all users on the system",
|
|
155
|
+
),
|
|
156
|
+
questionary.Choice(
|
|
157
|
+
title="user (User-level installation)",
|
|
158
|
+
value="user",
|
|
159
|
+
description="Install for current user only (recommended)",
|
|
160
|
+
),
|
|
161
|
+
questionary.Choice(
|
|
162
|
+
title="project (Project-specific installation)",
|
|
163
|
+
value="project",
|
|
164
|
+
description="Install for current project only",
|
|
165
|
+
),
|
|
166
|
+
questionary.Choice(title="Cancel", value="cancel"),
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
selected_scope = questionary.select(
|
|
170
|
+
"Select installation scope:",
|
|
171
|
+
choices=scope_choices,
|
|
172
|
+
qmark=">",
|
|
173
|
+
pointer=">",
|
|
174
|
+
default="user",
|
|
175
|
+
).ask()
|
|
176
|
+
|
|
177
|
+
if selected_scope == "cancel" or selected_scope is None:
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
return PresetScope(selected_scope)
|
|
181
|
+
|
|
182
|
+
def install_preset(self, strategy: dict, scope: PresetScope) -> bool:
|
|
183
|
+
"""
|
|
184
|
+
Install a preset strategy.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
strategy: Strategy dictionary
|
|
188
|
+
scope: Installation scope
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
True if successful, False otherwise
|
|
192
|
+
"""
|
|
193
|
+
from cl_preset.manager import Preset
|
|
194
|
+
|
|
195
|
+
if "path" not in strategy:
|
|
196
|
+
self.console.print("[red]Strategy path not found.[/red]")
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
source_path = strategy["path"]
|
|
200
|
+
|
|
201
|
+
# Try to read preset metadata
|
|
202
|
+
metadata_path = source_path / "preset.json"
|
|
203
|
+
if metadata_path.exists():
|
|
204
|
+
import json
|
|
205
|
+
|
|
206
|
+
metadata_data = json.loads(metadata_path.read_text())
|
|
207
|
+
metadata = PresetMetadata(**metadata_data)
|
|
208
|
+
else:
|
|
209
|
+
# Create metadata from directory name
|
|
210
|
+
metadata = PresetMetadata(
|
|
211
|
+
name=strategy["name"],
|
|
212
|
+
version="1.0.0",
|
|
213
|
+
description=strategy["description"],
|
|
214
|
+
preset_type=PresetType.STRATEGY,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
preset = Preset(
|
|
218
|
+
config={
|
|
219
|
+
"metadata": metadata,
|
|
220
|
+
"source_path": source_path,
|
|
221
|
+
"scope": scope,
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return self.preset_manager.install(preset)
|
|
226
|
+
|
|
227
|
+
def apply_git_strategy(self, strategy: dict, scope: PresetScope) -> bool:
|
|
228
|
+
"""
|
|
229
|
+
Apply a git strategy.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
strategy: Strategy dictionary
|
|
233
|
+
scope: Installation scope (for git strategies, affects where config is written)
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
True if successful, False otherwise
|
|
237
|
+
"""
|
|
238
|
+
import subprocess
|
|
239
|
+
|
|
240
|
+
name = strategy["name"]
|
|
241
|
+
git_strategy = self.git_manager.get_strategy(name)
|
|
242
|
+
|
|
243
|
+
if git_strategy is None:
|
|
244
|
+
self.console.print(f"[red]Strategy '{name}' not found.[/red]")
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
self.console.print(f"[cyan]Applying git strategy: {git_strategy.name}[/cyan]")
|
|
248
|
+
self.console.print(f"[dim]{git_strategy.description}[/dim]")
|
|
249
|
+
self.console.print()
|
|
250
|
+
|
|
251
|
+
# Check if we're in a git repository
|
|
252
|
+
try:
|
|
253
|
+
result = subprocess.run(
|
|
254
|
+
["git", "rev-parse", "--git-dir"],
|
|
255
|
+
capture_output=True,
|
|
256
|
+
text=True,
|
|
257
|
+
cwd=Path.cwd(),
|
|
258
|
+
)
|
|
259
|
+
if result.returncode != 0:
|
|
260
|
+
self.console.print(
|
|
261
|
+
"[red]Not in a git repository. Initialize one first with 'git init'[/red]"
|
|
262
|
+
)
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
except Exception as e:
|
|
266
|
+
self.console.print(f"[red]Error checking git repository: {e}[/red]")
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
# Create .moai/config/sections directory
|
|
270
|
+
config_dir = Path.cwd() / ".moai" / "config" / "sections"
|
|
271
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
272
|
+
|
|
273
|
+
# Export strategy config
|
|
274
|
+
strategy_file = config_dir / "git-strategy.yaml"
|
|
275
|
+
success = self.git_manager.export_strategy_yaml(name, strategy_file)
|
|
276
|
+
|
|
277
|
+
if success:
|
|
278
|
+
self.console.print()
|
|
279
|
+
self.console.print(
|
|
280
|
+
Panel(
|
|
281
|
+
f"[green]Strategy configuration written to {strategy_file}[/green]\n\n"
|
|
282
|
+
f"Main branch: [cyan]{git_strategy.main_branch}[/cyan]\n"
|
|
283
|
+
f"Feature pattern: [cyan]{git_strategy.branch_patterns.feature}[/cyan]",
|
|
284
|
+
title="Git Strategy Applied",
|
|
285
|
+
border_style="green",
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
return True
|
|
289
|
+
|
|
290
|
+
return False
|
|
291
|
+
|
|
292
|
+
def run(self) -> None:
|
|
293
|
+
"""Run the interactive CLI flow."""
|
|
294
|
+
self.show_welcome()
|
|
295
|
+
|
|
296
|
+
# Step 1: Select strategy
|
|
297
|
+
strategy = self.select_strategy()
|
|
298
|
+
if strategy is None:
|
|
299
|
+
self.console.print("[dim]Cancelled.[/dim]")
|
|
300
|
+
return
|
|
301
|
+
|
|
302
|
+
self.console.print()
|
|
303
|
+
|
|
304
|
+
# Step 2: Select scope
|
|
305
|
+
scope = self.select_scope()
|
|
306
|
+
if scope is None:
|
|
307
|
+
self.console.print("[dim]Cancelled.[/dim]")
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
self.console.print()
|
|
311
|
+
|
|
312
|
+
# Step 3: Install/Apply
|
|
313
|
+
strategy_type = strategy.get("type", "")
|
|
314
|
+
strategy_name = strategy["name"]
|
|
315
|
+
|
|
316
|
+
if strategy_type == "preset":
|
|
317
|
+
self.console.print(f"[cyan]Installing {strategy_name} to {scope.value} scope...[/cyan]")
|
|
318
|
+
success = self.install_preset(strategy, scope)
|
|
319
|
+
else: # git-strategy
|
|
320
|
+
self.console.print(
|
|
321
|
+
f"[cyan]Applying git strategy {strategy_name} to {scope.value} scope...[/cyan]"
|
|
322
|
+
)
|
|
323
|
+
success = self.apply_git_strategy(strategy, scope)
|
|
324
|
+
|
|
325
|
+
self.console.print()
|
|
326
|
+
|
|
327
|
+
# Step 4: Show result
|
|
328
|
+
if success:
|
|
329
|
+
self.console.print(
|
|
330
|
+
Panel(
|
|
331
|
+
"[bold green]Strategy installed successfully![/bold green]\n\n"
|
|
332
|
+
f"Type: [cyan]{strategy_type}[/cyan]\n"
|
|
333
|
+
f"Name: [cyan]{strategy_name}[/cyan]\n"
|
|
334
|
+
f"Scope: [cyan]{scope.value}[/cyan]",
|
|
335
|
+
title="Success",
|
|
336
|
+
border_style="green",
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
else:
|
|
340
|
+
self.console.print(
|
|
341
|
+
Panel(
|
|
342
|
+
"[bold red]Installation failed.[/bold red]\n\n"
|
|
343
|
+
"Please check the error messages above and try again.",
|
|
344
|
+
title="Error",
|
|
345
|
+
border_style="red",
|
|
346
|
+
)
|
|
347
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cl-preset
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A package for managing Claude Code configuration presets and strategies
|
|
5
5
|
Project-URL: Homepage, https://github.com/yarang/cl-preset
|
|
6
6
|
Project-URL: Documentation, https://github.com/yarang/cl-preset#readme
|
|
@@ -13,6 +13,7 @@ Requires-Python: >=3.10
|
|
|
13
13
|
Requires-Dist: click>=8.1.0
|
|
14
14
|
Requires-Dist: pydantic>=2.0.0
|
|
15
15
|
Requires-Dist: pyyaml>=6.0.0
|
|
16
|
+
Requires-Dist: questionary>=2.0.0
|
|
16
17
|
Requires-Dist: rich>=13.0.0
|
|
17
18
|
Provides-Extra: dev
|
|
18
19
|
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
@@ -52,6 +53,26 @@ uv pip install cl-preset
|
|
|
52
53
|
|
|
53
54
|
## Quick Start
|
|
54
55
|
|
|
56
|
+
### Interactive Mode (Recommended)
|
|
57
|
+
|
|
58
|
+
The easiest way to get started is the interactive mode:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cl-preset
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This will launch an interactive selector that guides you through:
|
|
65
|
+
1. **Welcome Screen** - Beautiful introduction to cl-preset
|
|
66
|
+
2. **Strategy Selection** - Browse and select from available strategies (presets and git strategies)
|
|
67
|
+
3. **Scope Selection** - Choose installation scope (global, user, or project)
|
|
68
|
+
4. **Installation** - Automatic installation with progress feedback
|
|
69
|
+
|
|
70
|
+
Available strategies include:
|
|
71
|
+
- **web-development-strategy** - Preset for web development with FastAPI and React
|
|
72
|
+
- **dual-repo-git-strategy** - Dev/Release repository separation workflow
|
|
73
|
+
- **github-flow-git-strategy** - Simple branch-based workflow
|
|
74
|
+
- **git-flow-git-strategy** - Feature/release/hotfix branching model
|
|
75
|
+
|
|
55
76
|
### Create a New Preset
|
|
56
77
|
|
|
57
78
|
```bash
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
cl_preset/__init__.py,sha256=rGIQS-1vuBzwnPj7NtC2N8hRIoMHUmSEMbWfnhjHjKQ,462
|
|
2
|
-
cl_preset/cli.py,sha256=
|
|
2
|
+
cl_preset/cli.py,sha256=f-WqvlfomDQbAaCmSfWLMtfs9KP-jm9E66NZEJC80OM,16003
|
|
3
3
|
cl_preset/git_strategy.py,sha256=FuiNUMaTwAyNA5nS8EvLhAntldJOBgRMO5FrUUgJ1K4,17375
|
|
4
|
+
cl_preset/interactive.py,sha256=O20q7GLjNvtY-4voRsqHIBM_b484IYuxOu8InzviWh8,11509
|
|
4
5
|
cl_preset/manager.py,sha256=OojhbTxXZ0cPt44PPZE1NqSBAXsGssB6ZuYyfoKwPa4,6192
|
|
5
6
|
cl_preset/models.py,sha256=UWx68lb0ac3VFCRpj3zOqc7qGwJpbX2EZcw_OYHBKWg,2241
|
|
6
7
|
cl_preset/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
8
|
cl_preset/templates/git_strategy_template.yaml,sha256=2oF97WOHq2crSRoaLzWodBvzANmQ-9FPzM-A0jui2iw,1328
|
|
8
|
-
cl_preset-0.
|
|
9
|
-
cl_preset-0.
|
|
10
|
-
cl_preset-0.
|
|
11
|
-
cl_preset-0.
|
|
12
|
-
cl_preset-0.
|
|
9
|
+
cl_preset-0.2.0.dist-info/METADATA,sha256=ohb0hWBIjOvk0zXFQkBr7Uf8QCnl11u8AEzTuQwwEAo,9782
|
|
10
|
+
cl_preset-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
11
|
+
cl_preset-0.2.0.dist-info/entry_points.txt,sha256=JAW_u5lrqylE516FDGO5audUKuCsxGYypQ7d6cA_9uY,49
|
|
12
|
+
cl_preset-0.2.0.dist-info/licenses/LICENSE,sha256=5b1_XulzMGBmVHcNKAHMo0Mqfqm5xJix4O5AErotK0o,1069
|
|
13
|
+
cl_preset-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|