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/utils/fs.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""File system utility functions."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def find_repo_root(start_path: Optional[Path] = None) -> Optional[Path]:
|
|
8
|
+
"""Find the root of a CLDPM mono repo by looking for cldpm.json.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
start_path: Starting directory to search from. Defaults to current directory.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Path to the repo root, or None if not found.
|
|
15
|
+
"""
|
|
16
|
+
if start_path is None:
|
|
17
|
+
start_path = Path.cwd()
|
|
18
|
+
|
|
19
|
+
current = start_path.resolve()
|
|
20
|
+
|
|
21
|
+
while current != current.parent:
|
|
22
|
+
if (current / "cldpm.json").exists():
|
|
23
|
+
return current
|
|
24
|
+
current = current.parent
|
|
25
|
+
|
|
26
|
+
# Check root directory
|
|
27
|
+
if (current / "cldpm.json").exists():
|
|
28
|
+
return current
|
|
29
|
+
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def ensure_dir(path: Path) -> Path:
|
|
34
|
+
"""Ensure a directory exists, creating it if necessary.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
path: Path to the directory.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
The path to the directory.
|
|
41
|
+
"""
|
|
42
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
return path
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_symlink(path: Path) -> bool:
|
|
47
|
+
"""Check if a path is a symlink.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
path: Path to check.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
True if the path is a symlink, False otherwise.
|
|
54
|
+
"""
|
|
55
|
+
return path.is_symlink()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def remove_symlinks_in_dir(directory: Path) -> None:
|
|
59
|
+
"""Remove all symlinks in a directory.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
directory: Directory to clean.
|
|
63
|
+
"""
|
|
64
|
+
if not directory.exists():
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
for item in directory.iterdir():
|
|
68
|
+
if item.is_symlink():
|
|
69
|
+
item.unlink()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def copy_dir_contents(src: Path, dst: Path, follow_symlinks: bool = True) -> None:
|
|
73
|
+
"""Copy directory contents, optionally resolving symlinks.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
src: Source directory.
|
|
77
|
+
dst: Destination directory.
|
|
78
|
+
follow_symlinks: If True, copy symlink targets. If False, copy symlinks as-is.
|
|
79
|
+
"""
|
|
80
|
+
import shutil
|
|
81
|
+
|
|
82
|
+
ensure_dir(dst)
|
|
83
|
+
|
|
84
|
+
for item in src.iterdir():
|
|
85
|
+
dest_path = dst / item.name
|
|
86
|
+
|
|
87
|
+
if item.is_symlink() and follow_symlinks:
|
|
88
|
+
# Resolve symlink and copy actual content
|
|
89
|
+
resolved = item.resolve()
|
|
90
|
+
if resolved.is_dir():
|
|
91
|
+
shutil.copytree(resolved, dest_path)
|
|
92
|
+
else:
|
|
93
|
+
shutil.copy2(resolved, dest_path)
|
|
94
|
+
elif item.is_dir():
|
|
95
|
+
shutil.copytree(item, dest_path, symlinks=not follow_symlinks)
|
|
96
|
+
else:
|
|
97
|
+
shutil.copy2(item, dest_path)
|
cldpm/utils/git.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Git utility functions for remote repository operations."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_github_token() -> Optional[str]:
|
|
13
|
+
"""Get GitHub token from environment variables.
|
|
14
|
+
|
|
15
|
+
Checks GITHUB_TOKEN and GH_TOKEN in that order.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
The token if found, None otherwise.
|
|
19
|
+
"""
|
|
20
|
+
return os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_repo_url(url: str) -> tuple[str, Optional[str], Optional[str]]:
|
|
24
|
+
"""Parse a repository URL to extract repo URL, optional path, and branch.
|
|
25
|
+
|
|
26
|
+
Supports formats:
|
|
27
|
+
- https://github.com/owner/repo
|
|
28
|
+
- https://github.com/owner/repo/tree/branch
|
|
29
|
+
- https://github.com/owner/repo/tree/branch/path/to/project
|
|
30
|
+
- github.com/owner/repo
|
|
31
|
+
- owner/repo (assumes GitHub)
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
url: The repository URL or shorthand.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Tuple of (repo_url, subpath, branch).
|
|
38
|
+
"""
|
|
39
|
+
original_url = url
|
|
40
|
+
branch = None
|
|
41
|
+
subpath = None
|
|
42
|
+
|
|
43
|
+
# Handle shorthand owner/repo format
|
|
44
|
+
if not url.startswith(("http://", "https://", "git@")) and "/" in url:
|
|
45
|
+
if url.count("/") == 1:
|
|
46
|
+
# Simple owner/repo format
|
|
47
|
+
url = f"https://github.com/{url}"
|
|
48
|
+
elif not url.startswith("github.com"):
|
|
49
|
+
url = f"https://{url}"
|
|
50
|
+
|
|
51
|
+
# Add https:// if missing
|
|
52
|
+
if url.startswith("github.com"):
|
|
53
|
+
url = f"https://{url}"
|
|
54
|
+
|
|
55
|
+
# Parse the URL
|
|
56
|
+
parsed = urlparse(url)
|
|
57
|
+
path_parts = parsed.path.strip("/").split("/")
|
|
58
|
+
|
|
59
|
+
if len(path_parts) >= 2:
|
|
60
|
+
owner = path_parts[0]
|
|
61
|
+
repo = path_parts[1]
|
|
62
|
+
|
|
63
|
+
# Remove .git suffix if present
|
|
64
|
+
if repo.endswith(".git"):
|
|
65
|
+
repo = repo[:-4]
|
|
66
|
+
|
|
67
|
+
# Check for /tree/branch/path pattern
|
|
68
|
+
if len(path_parts) > 3 and path_parts[2] == "tree":
|
|
69
|
+
branch = path_parts[3]
|
|
70
|
+
if len(path_parts) > 4:
|
|
71
|
+
subpath = "/".join(path_parts[4:])
|
|
72
|
+
|
|
73
|
+
repo_url = f"https://{parsed.netloc}/{owner}/{repo}.git"
|
|
74
|
+
return repo_url, subpath, branch
|
|
75
|
+
|
|
76
|
+
raise ValueError(f"Invalid repository URL: {original_url}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def clone_repo(
|
|
80
|
+
repo_url: str,
|
|
81
|
+
target_dir: Path,
|
|
82
|
+
branch: Optional[str] = None,
|
|
83
|
+
token: Optional[str] = None,
|
|
84
|
+
sparse_paths: Optional[list[str]] = None,
|
|
85
|
+
) -> Path:
|
|
86
|
+
"""Clone a git repository.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
repo_url: The repository URL.
|
|
90
|
+
target_dir: Directory to clone into.
|
|
91
|
+
branch: Optional branch to checkout.
|
|
92
|
+
token: Optional GitHub token for authentication.
|
|
93
|
+
sparse_paths: Optional list of paths for sparse checkout.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Path to the cloned repository.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
subprocess.CalledProcessError: If git commands fail.
|
|
100
|
+
"""
|
|
101
|
+
# Inject token into URL if provided
|
|
102
|
+
if token and "github.com" in repo_url:
|
|
103
|
+
repo_url = repo_url.replace(
|
|
104
|
+
"https://github.com", f"https://{token}@github.com"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
cmd = ["git", "clone", "--depth", "1"]
|
|
108
|
+
|
|
109
|
+
if branch:
|
|
110
|
+
cmd.extend(["--branch", branch])
|
|
111
|
+
|
|
112
|
+
if sparse_paths:
|
|
113
|
+
cmd.extend(["--filter=blob:none", "--sparse"])
|
|
114
|
+
|
|
115
|
+
cmd.extend([repo_url, str(target_dir)])
|
|
116
|
+
|
|
117
|
+
# Run clone with token hidden from output
|
|
118
|
+
env = os.environ.copy()
|
|
119
|
+
env["GIT_TERMINAL_PROMPT"] = "0"
|
|
120
|
+
|
|
121
|
+
subprocess.run(
|
|
122
|
+
cmd,
|
|
123
|
+
check=True,
|
|
124
|
+
capture_output=True,
|
|
125
|
+
text=True,
|
|
126
|
+
env=env,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Set up sparse checkout if needed
|
|
130
|
+
if sparse_paths:
|
|
131
|
+
subprocess.run(
|
|
132
|
+
["git", "sparse-checkout", "set"] + sparse_paths,
|
|
133
|
+
cwd=target_dir,
|
|
134
|
+
check=True,
|
|
135
|
+
capture_output=True,
|
|
136
|
+
text=True,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return target_dir
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def clone_to_temp(
|
|
143
|
+
repo_url: str,
|
|
144
|
+
branch: Optional[str] = None,
|
|
145
|
+
token: Optional[str] = None,
|
|
146
|
+
) -> Path:
|
|
147
|
+
"""Clone a repository to a temporary directory.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
repo_url: The repository URL.
|
|
151
|
+
branch: Optional branch to checkout.
|
|
152
|
+
token: Optional GitHub token for authentication.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Path to the temporary directory containing the clone.
|
|
156
|
+
"""
|
|
157
|
+
temp_dir = Path(tempfile.mkdtemp(prefix="cldpm-"))
|
|
158
|
+
clone_repo(repo_url, temp_dir, branch, token)
|
|
159
|
+
return temp_dir
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def cleanup_temp_dir(temp_dir: Path) -> None:
|
|
163
|
+
"""Clean up a temporary directory.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
temp_dir: Path to the temporary directory.
|
|
167
|
+
"""
|
|
168
|
+
if temp_dir.exists() and str(temp_dir).startswith(tempfile.gettempdir()):
|
|
169
|
+
shutil.rmtree(temp_dir)
|
cldpm/utils/output.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""CLI output formatting utilities using rich."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.tree import Tree
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def print_error(message: str) -> None:
|
|
14
|
+
"""Print an error message."""
|
|
15
|
+
console.print(f"[red]Error:[/red] {message}")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def print_success(message: str) -> None:
|
|
19
|
+
"""Print a success message."""
|
|
20
|
+
console.print(f"[green]✓[/green] {message}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def print_warning(message: str) -> None:
|
|
24
|
+
"""Print a warning message."""
|
|
25
|
+
console.print(f"[yellow]Warning:[/yellow] {message}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def print_info(message: str) -> None:
|
|
29
|
+
"""Print an info message."""
|
|
30
|
+
console.print(f"[blue]Info:[/blue] {message}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def print_tree(data: dict[str, Any], title: str = "Project") -> None:
|
|
34
|
+
"""Print a tree representation of project data.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
data: Project data dictionary.
|
|
38
|
+
title: Title for the tree.
|
|
39
|
+
"""
|
|
40
|
+
tree = Tree(f"[bold]{title}[/bold]: {data.get('name', 'Unknown')}")
|
|
41
|
+
|
|
42
|
+
# Add path
|
|
43
|
+
if "path" in data:
|
|
44
|
+
tree.add(f"[dim]Path:[/dim] {data['path']}")
|
|
45
|
+
|
|
46
|
+
# Add config info
|
|
47
|
+
if "config" in data:
|
|
48
|
+
config = data["config"]
|
|
49
|
+
config_branch = tree.add("[bold]Config[/bold]")
|
|
50
|
+
if config.get("description"):
|
|
51
|
+
config_branch.add(f"Description: {config['description']}")
|
|
52
|
+
|
|
53
|
+
# Add shared components
|
|
54
|
+
if "shared" in data:
|
|
55
|
+
shared = data["shared"]
|
|
56
|
+
has_shared = any(shared.get(t) for t in ["skills", "agents", "hooks", "rules"])
|
|
57
|
+
if has_shared:
|
|
58
|
+
shared_branch = tree.add("[bold]Shared Components[/bold] [dim](from shared/)[/dim]")
|
|
59
|
+
_add_component_items(shared_branch, shared)
|
|
60
|
+
|
|
61
|
+
# Add local components
|
|
62
|
+
if "local" in data:
|
|
63
|
+
local = data["local"]
|
|
64
|
+
has_local = any(local.get(t) for t in ["skills", "agents", "hooks", "rules"])
|
|
65
|
+
if has_local:
|
|
66
|
+
local_branch = tree.add("[bold]Local Components[/bold] [dim](project-specific)[/dim]")
|
|
67
|
+
_add_component_items(local_branch, local)
|
|
68
|
+
|
|
69
|
+
# Backward compatibility: handle old "resolved" format
|
|
70
|
+
if "resolved" in data and "shared" not in data:
|
|
71
|
+
resolved = data["resolved"]
|
|
72
|
+
deps_branch = tree.add("[bold]Dependencies[/bold]")
|
|
73
|
+
_add_component_items(deps_branch, resolved)
|
|
74
|
+
|
|
75
|
+
console.print(tree)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _add_component_items(parent_branch: Tree, components: dict[str, list]) -> None:
|
|
79
|
+
"""Add component items to a tree branch.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
parent_branch: The parent tree branch to add items to.
|
|
83
|
+
components: Dictionary mapping component types to lists of component info.
|
|
84
|
+
"""
|
|
85
|
+
for dep_type in ["skills", "agents", "hooks", "rules"]:
|
|
86
|
+
items = components.get(dep_type, [])
|
|
87
|
+
if items:
|
|
88
|
+
type_branch = parent_branch.add(f"[cyan]{dep_type}[/cyan]")
|
|
89
|
+
for item in items:
|
|
90
|
+
name = item['name']
|
|
91
|
+
comp_type = item.get('type', 'shared')
|
|
92
|
+
color = "green" if comp_type == "shared" else "yellow"
|
|
93
|
+
item_branch = type_branch.add(f"[{color}]{name}[/{color}]")
|
|
94
|
+
item_branch.add(f"[dim]Source:[/dim] {item['sourcePath']}")
|
|
95
|
+
if item.get("files"):
|
|
96
|
+
files_str = ", ".join(item["files"])
|
|
97
|
+
item_branch.add(f"[dim]Files:[/dim] {files_str}")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def print_dir_tree(path: Path, prefix: str = "", max_depth: int = 3, current_depth: int = 0) -> None:
|
|
101
|
+
"""Print a directory tree.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
path: Root path to display.
|
|
105
|
+
prefix: Prefix for indentation.
|
|
106
|
+
max_depth: Maximum depth to traverse.
|
|
107
|
+
current_depth: Current depth level.
|
|
108
|
+
"""
|
|
109
|
+
if current_depth >= max_depth:
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
if not path.exists():
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
items = sorted(path.iterdir(), key=lambda x: (not x.is_dir(), x.name))
|
|
116
|
+
|
|
117
|
+
for i, item in enumerate(items):
|
|
118
|
+
is_last = i == len(items) - 1
|
|
119
|
+
connector = "└── " if is_last else "├── "
|
|
120
|
+
|
|
121
|
+
# Format name with symlink indicator
|
|
122
|
+
name = item.name
|
|
123
|
+
if item.is_symlink():
|
|
124
|
+
target = item.resolve()
|
|
125
|
+
name = f"{name} -> {target}"
|
|
126
|
+
console.print(f"{prefix}{connector}[cyan]{name}[/cyan]")
|
|
127
|
+
elif item.is_dir():
|
|
128
|
+
console.print(f"{prefix}{connector}[bold blue]{name}/[/bold blue]")
|
|
129
|
+
# Recurse into directory
|
|
130
|
+
extension = " " if is_last else "│ "
|
|
131
|
+
print_dir_tree(item, prefix + extension, max_depth, current_depth + 1)
|
|
132
|
+
else:
|
|
133
|
+
console.print(f"{prefix}{connector}{name}")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cldpm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Claude Project Manager - SDK and CLI for mono repo management with Claude Code projects
|
|
5
|
+
Author-email: Aman Agarwal <agarwal.aman041@gmail.com>
|
|
6
|
+
Maintainer: Transilience.ai
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: click>=8.1.0
|
|
10
|
+
Requires-Dist: jinja2>=3.1.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0.0
|
|
12
|
+
Requires-Dist: rich>=13.0.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
cldpm/__init__.py,sha256=Qs_cOf9f0je2K86JhYplR5GXO3pg28CXGgWp-3ANme8,343
|
|
2
|
+
cldpm/__main__.py,sha256=KLtPDZNHEkoLu4WDS7YiJAu5-rUfAOX0DMaRJp85l9k,111
|
|
3
|
+
cldpm/_banner.py,sha256=g0NPFBHAOPJRVOuk8aXOzGbNZV5Gg7jd3danaW_pE2U,4581
|
|
4
|
+
cldpm/cli.py,sha256=8KTeFh1Pw_klOxyNYLaICfHo4PIargDSzeIvIv6Y_lM,2023
|
|
5
|
+
cldpm/commands/__init__.py,sha256=v7fUbesCKq_3oGCjjs_8XnQJD3uQIMCgekSRPT3PDx8,318
|
|
6
|
+
cldpm/commands/add.py,sha256=qXidhVSjF2Ekq6AWNda4QwMi54XiU4elzJ6Tl78qHHc,7004
|
|
7
|
+
cldpm/commands/clone.py,sha256=6_FlzB1p0s9hkTmS1etIiUXWYMRlB2laONepJN8QtKo,6492
|
|
8
|
+
cldpm/commands/create.py,sha256=hE30b5ys_ILI-BGTAsJralh2qt8AZyDyqYVB5U1rnhE,13348
|
|
9
|
+
cldpm/commands/get.py,sha256=AnDDfJHFO_9yJnELXkKVmpNo78r6bCNPLM3Q_8HXsZ4,12188
|
|
10
|
+
cldpm/commands/init.py,sha256=LG8l57yADPjTMNRrPoXiZqPO-678DEPdhIF7fwJDOqQ,10798
|
|
11
|
+
cldpm/commands/link.py,sha256=cgw5IxUC9Y3267JMQlq1dERIPnajpUDFzR7t0vmTNXQ,9927
|
|
12
|
+
cldpm/commands/remove.py,sha256=e9lBWLJtr4w045L8xi59lvgjIXt9_6mtG0Ux1t3vDxU,9788
|
|
13
|
+
cldpm/commands/sync.py,sha256=y5_1SSg5IrpJvj9qnqGt_MiNTqdGKO4NTw7C2wNFF5E,3230
|
|
14
|
+
cldpm/core/__init__.py,sha256=usYkD2Dyhgo4z5EZyaM4WjmaUxelQRbC7glscAZetww,662
|
|
15
|
+
cldpm/core/config.py,sha256=lt050-9TTjvrZWWl7klAyYRJON7mIfjPdfEc1dJijn0,5209
|
|
16
|
+
cldpm/core/linker.py,sha256=h46XI-KOVF7LPpHRMqEY5-9Ze5E3RUzL1ChttN3mr3E,8324
|
|
17
|
+
cldpm/core/resolver.py,sha256=deBNvIz6FksHUEnA-ArIfzxTtqht0pVNfx_vYO-fLPY,8951
|
|
18
|
+
cldpm/schemas/__init__.py,sha256=G4jArynDyLTbYsxAtG8Fayt4sRbQuaV_FTK1YoMtB8U,342
|
|
19
|
+
cldpm/schemas/cldpm.py,sha256=yOmkEwO3SHZ5fABBTsy0cGwjoseLeyLt_VZoBSWvroY,925
|
|
20
|
+
cldpm/schemas/component.py,sha256=lcph8DH89g70Upzd3zjIO56eFbKi0DKDyg2QWR4ylNU,709
|
|
21
|
+
cldpm/schemas/project.py,sha256=fKZ1gqPszr9tL6s2zOTyFn6bAzoFZ5wK_FyCJNRdwWs,1394
|
|
22
|
+
cldpm/templates/CLAUDE.md.j2,sha256=IgpGcmnDptdq5WMjDOWkLHcKstQwx2QT7btHDadmvOs,612
|
|
23
|
+
cldpm/templates/ROOT_CLAUDE.md.j2,sha256=U7O7shBi5tRV3X7JyXPdgHQVarj0aM4fdR6-08P0_68,786
|
|
24
|
+
cldpm/templates/agent.md.j2,sha256=S_dpcCk9xU4iLb5svygf-2cmT289gYpzesa4-GqhuFc,301
|
|
25
|
+
cldpm/templates/gitignore.j2,sha256=hYWP9nFtIxF6sQuSxEKrLqsyt657LT1CV4XRuB4upIc,540
|
|
26
|
+
cldpm/templates/hook.md.j2,sha256=rRcf9VtBxK7haI8oMckwvvk17nxpqvUh2Wk_MloavGE,364
|
|
27
|
+
cldpm/templates/rule.md.j2,sha256=-xHNF_ZqnyitdGeElZYOl0KRIk8hwO_3I4nb6fXm5z0,346
|
|
28
|
+
cldpm/templates/skill.md.j2,sha256=ToYa_4ce7O6SvXD8MCoJs7grMwrJiIbpPVCRkAFQ1ew,259
|
|
29
|
+
cldpm/utils/__init__.py,sha256=xfTXT57qFYgHvaEnt_-sFqFv_E5leuLMvY5nX1Jog3o,568
|
|
30
|
+
cldpm/utils/fs.py,sha256=BugmwOy6XE9V14nTZ2pCOrfENKnC6c1yP0lsigyamLw,2422
|
|
31
|
+
cldpm/utils/git.py,sha256=Ht1Tf2jXVFEDkJ6SW8zWJHawBdvqE5XLmz8UYvQp17A,4524
|
|
32
|
+
cldpm/utils/output.py,sha256=NycyKlZofXX9zI7qVt7OXGr8XInOZehrcUQp7qVIWi0,4556
|
|
33
|
+
cldpm-0.1.0.dist-info/METADATA,sha256=wRTH0GJ8j6BUr-6crZt8hK63kiDPUed6lxx1qKy3z2o,505
|
|
34
|
+
cldpm-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
35
|
+
cldpm-0.1.0.dist-info/entry_points.txt,sha256=ioYXXcFtreeoAKVT97JZ_OR75PAh9HNAXA8O9vS2N1M,46
|
|
36
|
+
cldpm-0.1.0.dist-info/licenses/LICENSE,sha256=b0CSRDshyAcy5gblTvD30bCx64DEiD5vxQsHbp0-3Hk,1072
|
|
37
|
+
cldpm-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Transilience.ai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|