cldpm 0.1.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.
- cldpm/__init__.py +12 -0
- cldpm/__main__.py +6 -0
- cldpm/_banner.py +99 -0
- cldpm/cli.py +81 -0
- cldpm/commands/__init__.py +12 -0
- cldpm/commands/add.py +206 -0
- cldpm/commands/clone.py +184 -0
- cldpm/commands/create.py +418 -0
- cldpm/commands/get.py +375 -0
- cldpm/commands/init.py +331 -0
- cldpm/commands/link.py +320 -0
- cldpm/commands/remove.py +289 -0
- cldpm/commands/sync.py +91 -0
- cldpm/core/__init__.py +26 -0
- cldpm/core/config.py +182 -0
- cldpm/core/linker.py +265 -0
- cldpm/core/resolver.py +291 -0
- cldpm/schemas/__init__.py +13 -0
- cldpm/schemas/cldpm.py +32 -0
- cldpm/schemas/component.py +24 -0
- cldpm/schemas/project.py +42 -0
- cldpm/templates/CLAUDE.md.j2 +22 -0
- cldpm/templates/ROOT_CLAUDE.md.j2 +34 -0
- cldpm/templates/agent.md.j2 +22 -0
- cldpm/templates/gitignore.j2 +43 -0
- cldpm/templates/hook.md.j2 +20 -0
- cldpm/templates/rule.md.j2 +33 -0
- cldpm/templates/skill.md.j2 +15 -0
- cldpm/utils/__init__.py +27 -0
- cldpm/utils/fs.py +97 -0
- cldpm/utils/git.py +169 -0
- cldpm/utils/output.py +133 -0
- cldpm-0.1.0.dist-info/METADATA +15 -0
- cldpm-0.1.0.dist-info/RECORD +37 -0
- cldpm-0.1.0.dist-info/WHEEL +4 -0
- cldpm-0.1.0.dist-info/entry_points.txt +2 -0
- cldpm-0.1.0.dist-info/licenses/LICENSE +21 -0
cldpm/commands/create.py
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"""Implementation of cldpm create command."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from jinja2 import Environment, PackageLoader
|
|
9
|
+
|
|
10
|
+
from ..schemas import ComponentDependencies, ComponentMetadata, ProjectConfig, ProjectDependencies
|
|
11
|
+
from ..core.config import load_cldpm_config, save_project_config
|
|
12
|
+
from ..core.linker import sync_project_links
|
|
13
|
+
from ..utils.fs import ensure_dir, find_repo_root
|
|
14
|
+
from ..utils.output import print_success, print_error, print_dir_tree, console
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_dependency_list(deps_str: Optional[str]) -> list[str]:
|
|
18
|
+
"""Parse a comma-separated dependency string into a list."""
|
|
19
|
+
if not deps_str:
|
|
20
|
+
return []
|
|
21
|
+
return [d.strip() for d in deps_str.split(",") if d.strip()]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def save_component_metadata(
|
|
25
|
+
metadata: ComponentMetadata,
|
|
26
|
+
component_path: Path,
|
|
27
|
+
component_type: str,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Save component metadata to a JSON file.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
metadata: The ComponentMetadata to save.
|
|
33
|
+
component_path: Path to the component directory.
|
|
34
|
+
component_type: Type of component (skills, agents, hooks, rules).
|
|
35
|
+
"""
|
|
36
|
+
singular_type = component_type.rstrip("s") # skills -> skill
|
|
37
|
+
metadata_path = component_path / f"{singular_type}.json"
|
|
38
|
+
|
|
39
|
+
data = {"name": metadata.name}
|
|
40
|
+
if metadata.description:
|
|
41
|
+
data["description"] = metadata.description
|
|
42
|
+
|
|
43
|
+
# Only include dependencies if any exist
|
|
44
|
+
deps = metadata.dependencies
|
|
45
|
+
if deps.skills or deps.agents or deps.hooks or deps.rules:
|
|
46
|
+
data["dependencies"] = {}
|
|
47
|
+
if deps.skills:
|
|
48
|
+
data["dependencies"]["skills"] = deps.skills
|
|
49
|
+
if deps.agents:
|
|
50
|
+
data["dependencies"]["agents"] = deps.agents
|
|
51
|
+
if deps.hooks:
|
|
52
|
+
data["dependencies"]["hooks"] = deps.hooks
|
|
53
|
+
if deps.rules:
|
|
54
|
+
data["dependencies"]["rules"] = deps.rules
|
|
55
|
+
|
|
56
|
+
with open(metadata_path, "w") as f:
|
|
57
|
+
json.dump(data, f, indent=2)
|
|
58
|
+
f.write("\n")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@click.group()
|
|
62
|
+
def create() -> None:
|
|
63
|
+
"""Create new projects or components."""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@create.command()
|
|
68
|
+
@click.argument("name")
|
|
69
|
+
@click.option("--description", "-d", help="Project description")
|
|
70
|
+
@click.option("--skills", "-s", help="Comma-separated list of shared skills to add")
|
|
71
|
+
@click.option("--agents", "-a", help="Comma-separated list of shared agents to add")
|
|
72
|
+
def project(
|
|
73
|
+
name: str,
|
|
74
|
+
description: Optional[str],
|
|
75
|
+
skills: Optional[str],
|
|
76
|
+
agents: Optional[str],
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Create a new project in the mono repo.
|
|
79
|
+
|
|
80
|
+
Creates a project with the standard structure for Claude Code, including
|
|
81
|
+
directories for both shared (symlinked) and local (project-specific)
|
|
82
|
+
components.
|
|
83
|
+
|
|
84
|
+
\b
|
|
85
|
+
Structure created:
|
|
86
|
+
project.json - Project manifest (tracks shared dependencies)
|
|
87
|
+
CLAUDE.md - Project instructions
|
|
88
|
+
.claude/ - Components directory
|
|
89
|
+
skills/ - Skills (shared symlinks + local)
|
|
90
|
+
agents/ - Agents (shared symlinks + local)
|
|
91
|
+
hooks/ - Hooks (shared symlinks + local)
|
|
92
|
+
rules/ - Rules (shared symlinks + local)
|
|
93
|
+
outputs/ - Project outputs
|
|
94
|
+
|
|
95
|
+
\b
|
|
96
|
+
Examples:
|
|
97
|
+
cldpm create project my-app
|
|
98
|
+
cldpm create project my-app -d "My application"
|
|
99
|
+
cldpm create project my-app --skills skill1,skill2
|
|
100
|
+
cldpm create project my-app -s skill1 -a agent1
|
|
101
|
+
"""
|
|
102
|
+
# Find repo root
|
|
103
|
+
repo_root = find_repo_root()
|
|
104
|
+
if repo_root is None:
|
|
105
|
+
print_error("Not in a CLDPM mono repo. Run 'cldpm init' first.")
|
|
106
|
+
raise SystemExit(1)
|
|
107
|
+
|
|
108
|
+
# Load config
|
|
109
|
+
cldpm_config = load_cldpm_config(repo_root)
|
|
110
|
+
|
|
111
|
+
# Create project directory
|
|
112
|
+
project_path = repo_root / cldpm_config.projects_dir / name
|
|
113
|
+
|
|
114
|
+
if project_path.exists():
|
|
115
|
+
print_error(f"Project already exists: {name}")
|
|
116
|
+
raise SystemExit(1)
|
|
117
|
+
|
|
118
|
+
ensure_dir(project_path)
|
|
119
|
+
|
|
120
|
+
# Parse dependencies
|
|
121
|
+
deps = ProjectDependencies()
|
|
122
|
+
if skills:
|
|
123
|
+
deps.skills = [s.strip() for s in skills.split(",") if s.strip()]
|
|
124
|
+
if agents:
|
|
125
|
+
deps.agents = [a.strip() for a in agents.split(",") if a.strip()]
|
|
126
|
+
|
|
127
|
+
# Create project config
|
|
128
|
+
project_config = ProjectConfig(
|
|
129
|
+
name=name,
|
|
130
|
+
description=description,
|
|
131
|
+
dependencies=deps,
|
|
132
|
+
)
|
|
133
|
+
save_project_config(project_config, project_path)
|
|
134
|
+
|
|
135
|
+
# Create .claude directory structure
|
|
136
|
+
claude_dir = project_path / ".claude"
|
|
137
|
+
ensure_dir(claude_dir)
|
|
138
|
+
ensure_dir(claude_dir / "skills")
|
|
139
|
+
ensure_dir(claude_dir / "agents")
|
|
140
|
+
ensure_dir(claude_dir / "hooks")
|
|
141
|
+
ensure_dir(claude_dir / "rules")
|
|
142
|
+
|
|
143
|
+
# Create settings.json placeholder
|
|
144
|
+
(claude_dir / "settings.json").write_text("{}\n")
|
|
145
|
+
|
|
146
|
+
# Create outputs directory
|
|
147
|
+
ensure_dir(project_path / "outputs")
|
|
148
|
+
|
|
149
|
+
# Create CLAUDE.md from template
|
|
150
|
+
env = Environment(loader=PackageLoader("cldpm", "templates"))
|
|
151
|
+
template = env.get_template("CLAUDE.md.j2")
|
|
152
|
+
claude_md = template.render(
|
|
153
|
+
project_name=name,
|
|
154
|
+
description=description or "",
|
|
155
|
+
)
|
|
156
|
+
(project_path / "CLAUDE.md").write_text(claude_md)
|
|
157
|
+
|
|
158
|
+
print_success(f"Created project: {name}")
|
|
159
|
+
|
|
160
|
+
# Sync symlinks if dependencies were specified
|
|
161
|
+
if deps.skills or deps.agents:
|
|
162
|
+
result = sync_project_links(project_path, repo_root)
|
|
163
|
+
if result["created"]:
|
|
164
|
+
console.print(f" Linked: {', '.join(result['created'])}")
|
|
165
|
+
if result["missing"]:
|
|
166
|
+
console.print(f" [yellow]Missing:[/yellow] {', '.join(result['missing'])}")
|
|
167
|
+
|
|
168
|
+
console.print()
|
|
169
|
+
print_dir_tree(project_path, max_depth=2)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@create.command()
|
|
173
|
+
@click.argument("name")
|
|
174
|
+
@click.option("--description", "-d", help="Skill description")
|
|
175
|
+
@click.option("--skills", "-s", help="Comma-separated list of skill dependencies")
|
|
176
|
+
@click.option("--hooks", "-h", "hooks_deps", help="Comma-separated list of hook dependencies")
|
|
177
|
+
@click.option("--rules", "-r", help="Comma-separated list of rule dependencies")
|
|
178
|
+
def skill(
|
|
179
|
+
name: str,
|
|
180
|
+
description: Optional[str],
|
|
181
|
+
skills: Optional[str],
|
|
182
|
+
hooks_deps: Optional[str],
|
|
183
|
+
rules: Optional[str],
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Create a new shared skill.
|
|
186
|
+
|
|
187
|
+
Creates a skill in the shared/skills/ directory with optional dependencies
|
|
188
|
+
on other shared components.
|
|
189
|
+
|
|
190
|
+
\b
|
|
191
|
+
Structure created:
|
|
192
|
+
shared/skills/<name>/
|
|
193
|
+
SKILL.md - Skill instructions
|
|
194
|
+
skill.json - Skill metadata and dependencies
|
|
195
|
+
|
|
196
|
+
\b
|
|
197
|
+
Examples:
|
|
198
|
+
cldpm create skill code-review
|
|
199
|
+
cldpm create skill code-review -d "Code review assistant"
|
|
200
|
+
cldpm create skill advanced-review --skills base-review,utils
|
|
201
|
+
cldpm create skill full-check -s lint-check -h pre-commit -r security
|
|
202
|
+
"""
|
|
203
|
+
_create_component("skills", name, description, skills, None, hooks_deps, rules)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@create.command()
|
|
207
|
+
@click.argument("name")
|
|
208
|
+
@click.option("--description", "-d", help="Agent description")
|
|
209
|
+
@click.option("--skills", "-s", help="Comma-separated list of skill dependencies")
|
|
210
|
+
@click.option("--agents", "-a", help="Comma-separated list of agent dependencies")
|
|
211
|
+
@click.option("--hooks", "-h", "hooks_deps", help="Comma-separated list of hook dependencies")
|
|
212
|
+
@click.option("--rules", "-r", help="Comma-separated list of rule dependencies")
|
|
213
|
+
def agent(
|
|
214
|
+
name: str,
|
|
215
|
+
description: Optional[str],
|
|
216
|
+
skills: Optional[str],
|
|
217
|
+
agents: Optional[str],
|
|
218
|
+
hooks_deps: Optional[str],
|
|
219
|
+
rules: Optional[str],
|
|
220
|
+
) -> None:
|
|
221
|
+
"""Create a new shared agent.
|
|
222
|
+
|
|
223
|
+
Creates an agent in the shared/agents/ directory with optional dependencies
|
|
224
|
+
on other shared components.
|
|
225
|
+
|
|
226
|
+
\b
|
|
227
|
+
Structure created:
|
|
228
|
+
shared/agents/<name>/
|
|
229
|
+
AGENT.md - Agent instructions
|
|
230
|
+
agent.json - Agent metadata and dependencies
|
|
231
|
+
|
|
232
|
+
\b
|
|
233
|
+
Examples:
|
|
234
|
+
cldpm create agent debugger
|
|
235
|
+
cldpm create agent debugger -d "Debugging assistant"
|
|
236
|
+
cldpm create agent security-audit --skills vuln-scan,code-review
|
|
237
|
+
cldpm create agent full-audit -s scan -a reviewer -h pre-commit -r security
|
|
238
|
+
"""
|
|
239
|
+
_create_component("agents", name, description, skills, agents, hooks_deps, rules)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@create.command()
|
|
243
|
+
@click.argument("name")
|
|
244
|
+
@click.option("--description", "-d", help="Hook description")
|
|
245
|
+
@click.option("--skills", "-s", help="Comma-separated list of skill dependencies")
|
|
246
|
+
@click.option("--hooks", "-h", "hooks_deps", help="Comma-separated list of hook dependencies")
|
|
247
|
+
@click.option("--rules", "-r", help="Comma-separated list of rule dependencies")
|
|
248
|
+
def hook(
|
|
249
|
+
name: str,
|
|
250
|
+
description: Optional[str],
|
|
251
|
+
skills: Optional[str],
|
|
252
|
+
hooks_deps: Optional[str],
|
|
253
|
+
rules: Optional[str],
|
|
254
|
+
) -> None:
|
|
255
|
+
"""Create a new shared hook.
|
|
256
|
+
|
|
257
|
+
Creates a hook in the shared/hooks/ directory with optional dependencies
|
|
258
|
+
on other shared components.
|
|
259
|
+
|
|
260
|
+
\b
|
|
261
|
+
Structure created:
|
|
262
|
+
shared/hooks/<name>/
|
|
263
|
+
HOOK.md - Hook instructions
|
|
264
|
+
hook.json - Hook metadata and dependencies
|
|
265
|
+
|
|
266
|
+
\b
|
|
267
|
+
Examples:
|
|
268
|
+
cldpm create hook pre-commit
|
|
269
|
+
cldpm create hook pre-commit -d "Pre-commit validation"
|
|
270
|
+
cldpm create hook full-validate --skills lint,format --rules style
|
|
271
|
+
"""
|
|
272
|
+
_create_component("hooks", name, description, skills, None, hooks_deps, rules)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@create.command()
|
|
276
|
+
@click.argument("name")
|
|
277
|
+
@click.option("--description", "-d", help="Rule description")
|
|
278
|
+
@click.option("--rules", "-r", help="Comma-separated list of rule dependencies")
|
|
279
|
+
def rule(
|
|
280
|
+
name: str,
|
|
281
|
+
description: Optional[str],
|
|
282
|
+
rules: Optional[str],
|
|
283
|
+
) -> None:
|
|
284
|
+
"""Create a new shared rule.
|
|
285
|
+
|
|
286
|
+
Creates a rule in the shared/rules/ directory with optional dependencies
|
|
287
|
+
on other rules.
|
|
288
|
+
|
|
289
|
+
\b
|
|
290
|
+
Structure created:
|
|
291
|
+
shared/rules/<name>/
|
|
292
|
+
RULE.md - Rule instructions
|
|
293
|
+
rule.json - Rule metadata and dependencies
|
|
294
|
+
|
|
295
|
+
\b
|
|
296
|
+
Examples:
|
|
297
|
+
cldpm create rule security
|
|
298
|
+
cldpm create rule security -d "Security guidelines"
|
|
299
|
+
cldpm create rule full-compliance --rules security,privacy,logging
|
|
300
|
+
"""
|
|
301
|
+
_create_component("rules", name, description, None, None, None, rules)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _create_component(
|
|
305
|
+
component_type: str,
|
|
306
|
+
name: str,
|
|
307
|
+
description: Optional[str],
|
|
308
|
+
skills: Optional[str],
|
|
309
|
+
agents: Optional[str],
|
|
310
|
+
hooks: Optional[str],
|
|
311
|
+
rules: Optional[str],
|
|
312
|
+
) -> None:
|
|
313
|
+
"""Create a shared component.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
component_type: Type of component (skills, agents, hooks, rules).
|
|
317
|
+
name: Component name.
|
|
318
|
+
description: Component description.
|
|
319
|
+
skills: Comma-separated skill dependencies.
|
|
320
|
+
agents: Comma-separated agent dependencies.
|
|
321
|
+
hooks: Comma-separated hook dependencies.
|
|
322
|
+
rules: Comma-separated rule dependencies.
|
|
323
|
+
"""
|
|
324
|
+
# Find repo root
|
|
325
|
+
repo_root = find_repo_root()
|
|
326
|
+
if repo_root is None:
|
|
327
|
+
print_error("Not in a CLDPM mono repo. Run 'cldpm init' first.")
|
|
328
|
+
raise SystemExit(1)
|
|
329
|
+
|
|
330
|
+
# Load config
|
|
331
|
+
cldpm_config = load_cldpm_config(repo_root)
|
|
332
|
+
|
|
333
|
+
# Create component directory
|
|
334
|
+
component_path = repo_root / cldpm_config.shared_dir / component_type / name
|
|
335
|
+
|
|
336
|
+
if component_path.exists():
|
|
337
|
+
print_error(f"Component already exists: {component_type}/{name}")
|
|
338
|
+
raise SystemExit(1)
|
|
339
|
+
|
|
340
|
+
ensure_dir(component_path)
|
|
341
|
+
|
|
342
|
+
# Parse dependencies
|
|
343
|
+
deps = ComponentDependencies(
|
|
344
|
+
skills=parse_dependency_list(skills),
|
|
345
|
+
agents=parse_dependency_list(agents),
|
|
346
|
+
hooks=parse_dependency_list(hooks),
|
|
347
|
+
rules=parse_dependency_list(rules),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# Create metadata
|
|
351
|
+
metadata = ComponentMetadata(
|
|
352
|
+
name=name,
|
|
353
|
+
description=description,
|
|
354
|
+
dependencies=deps,
|
|
355
|
+
)
|
|
356
|
+
save_component_metadata(metadata, component_path, component_type)
|
|
357
|
+
|
|
358
|
+
# Create content file from template
|
|
359
|
+
singular_type = component_type.rstrip("s") # skills -> skill
|
|
360
|
+
content_filename = f"{singular_type.upper()}.md"
|
|
361
|
+
|
|
362
|
+
env = Environment(loader=PackageLoader("cldpm", "templates"))
|
|
363
|
+
|
|
364
|
+
# Try to load component-specific template, fall back to generic
|
|
365
|
+
try:
|
|
366
|
+
template = env.get_template(f"{singular_type}.md.j2")
|
|
367
|
+
except Exception:
|
|
368
|
+
# Use generic template
|
|
369
|
+
template_content = f"""# {name}
|
|
370
|
+
|
|
371
|
+
{description or f"A shared {singular_type}."}
|
|
372
|
+
|
|
373
|
+
## Overview
|
|
374
|
+
|
|
375
|
+
Describe what this {singular_type} does.
|
|
376
|
+
|
|
377
|
+
## Usage
|
|
378
|
+
|
|
379
|
+
Explain how to use this {singular_type}.
|
|
380
|
+
"""
|
|
381
|
+
(component_path / content_filename).write_text(template_content)
|
|
382
|
+
_print_component_success(component_type, name, deps, component_path)
|
|
383
|
+
return
|
|
384
|
+
|
|
385
|
+
content = template.render(
|
|
386
|
+
name=name,
|
|
387
|
+
description=description or "",
|
|
388
|
+
)
|
|
389
|
+
(component_path / content_filename).write_text(content)
|
|
390
|
+
|
|
391
|
+
_print_component_success(component_type, name, deps, component_path)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _print_component_success(
|
|
395
|
+
component_type: str,
|
|
396
|
+
name: str,
|
|
397
|
+
deps: ComponentDependencies,
|
|
398
|
+
component_path: Path,
|
|
399
|
+
) -> None:
|
|
400
|
+
"""Print success message for component creation."""
|
|
401
|
+
singular_type = component_type.rstrip("s")
|
|
402
|
+
print_success(f"Created {singular_type}: {name}")
|
|
403
|
+
|
|
404
|
+
# Show dependencies if any
|
|
405
|
+
all_deps = []
|
|
406
|
+
if deps.skills:
|
|
407
|
+
all_deps.extend([f"skills/{s}" for s in deps.skills])
|
|
408
|
+
if deps.agents:
|
|
409
|
+
all_deps.extend([f"agents/{a}" for a in deps.agents])
|
|
410
|
+
if deps.hooks:
|
|
411
|
+
all_deps.extend([f"hooks/{h}" for h in deps.hooks])
|
|
412
|
+
if deps.rules:
|
|
413
|
+
all_deps.extend([f"rules/{r}" for r in deps.rules])
|
|
414
|
+
|
|
415
|
+
if all_deps:
|
|
416
|
+
console.print(f" Dependencies: {', '.join(all_deps)}")
|
|
417
|
+
|
|
418
|
+
console.print(f" Location: {component_path}")
|