agr 0.4.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.
- agr/__init__.py +3 -0
- agr/cli/__init__.py +5 -0
- agr/cli/add.py +132 -0
- agr/cli/common.py +1085 -0
- agr/cli/init.py +292 -0
- agr/cli/main.py +34 -0
- agr/cli/remove.py +125 -0
- agr/cli/run.py +385 -0
- agr/cli/sync.py +263 -0
- agr/cli/update.py +140 -0
- agr/config.py +187 -0
- agr/exceptions.py +33 -0
- agr/fetcher.py +781 -0
- agr/github.py +95 -0
- agr/scaffold.py +194 -0
- agr-0.4.0.dist-info/METADATA +17 -0
- agr-0.4.0.dist-info/RECORD +19 -0
- agr-0.4.0.dist-info/WHEEL +4 -0
- agr-0.4.0.dist-info/entry_points.txt +3 -0
agr/cli/init.py
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Init subcommand for agr - create new resources and repos."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from agr.github import check_gh_cli, create_github_repo, get_github_username, repo_exists
|
|
10
|
+
from agr.scaffold import (
|
|
11
|
+
create_agent_resources_repo,
|
|
12
|
+
init_git,
|
|
13
|
+
scaffold_repo,
|
|
14
|
+
write_gitignore,
|
|
15
|
+
write_readme,
|
|
16
|
+
write_starter_agent,
|
|
17
|
+
write_starter_command,
|
|
18
|
+
write_starter_skill,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
app = typer.Typer(
|
|
24
|
+
help="Create new agent resources or repositories.",
|
|
25
|
+
no_args_is_help=True,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command("repo")
|
|
30
|
+
def init_repo(
|
|
31
|
+
name: Annotated[
|
|
32
|
+
str,
|
|
33
|
+
typer.Argument(
|
|
34
|
+
help="Name of the repository to create, or '.' for current directory",
|
|
35
|
+
metavar="NAME",
|
|
36
|
+
),
|
|
37
|
+
] = "agent-resources",
|
|
38
|
+
path: Annotated[
|
|
39
|
+
Path | None,
|
|
40
|
+
typer.Option(
|
|
41
|
+
"--path",
|
|
42
|
+
"-p",
|
|
43
|
+
help="Custom path for the repository (default: ./<name>)",
|
|
44
|
+
),
|
|
45
|
+
] = None,
|
|
46
|
+
github: Annotated[
|
|
47
|
+
bool,
|
|
48
|
+
typer.Option(
|
|
49
|
+
"--github",
|
|
50
|
+
"-g",
|
|
51
|
+
help="Create a GitHub repository and push",
|
|
52
|
+
),
|
|
53
|
+
] = False,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Create a new agent-resources repository with starter content.
|
|
56
|
+
|
|
57
|
+
Creates a directory structure with example skill, command, and agent files.
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
agr init repo # Creates ./agent-resources/
|
|
61
|
+
agr init repo my-resources # Creates ./my-resources/
|
|
62
|
+
agr init repo . # Initialize in current directory
|
|
63
|
+
agr init repo agent-resources --github # Creates and pushes to GitHub
|
|
64
|
+
"""
|
|
65
|
+
# Determine target path
|
|
66
|
+
if name == ".":
|
|
67
|
+
target_path = Path.cwd()
|
|
68
|
+
# Use directory name for GitHub repo name
|
|
69
|
+
name = target_path.name
|
|
70
|
+
else:
|
|
71
|
+
target_path = path or Path.cwd() / name
|
|
72
|
+
|
|
73
|
+
if target_path.exists():
|
|
74
|
+
console.print(f"[red]Error: Directory already exists: {target_path}[/red]")
|
|
75
|
+
raise typer.Exit(1)
|
|
76
|
+
|
|
77
|
+
# Check if .claude directory already exists
|
|
78
|
+
claude_dir = target_path / ".claude"
|
|
79
|
+
if claude_dir.exists():
|
|
80
|
+
console.print(f"[red]Error: .claude directory already exists at {claude_dir}[/red]")
|
|
81
|
+
raise typer.Exit(1)
|
|
82
|
+
|
|
83
|
+
# Check GitHub CLI if --github flag is set
|
|
84
|
+
if github:
|
|
85
|
+
if not check_gh_cli():
|
|
86
|
+
console.print(
|
|
87
|
+
"[red]Error: GitHub CLI (gh) is not installed or not authenticated.[/red]\n"
|
|
88
|
+
"Install it from https://cli.github.com/ and run 'gh auth login'"
|
|
89
|
+
)
|
|
90
|
+
raise typer.Exit(1)
|
|
91
|
+
|
|
92
|
+
if repo_exists(name):
|
|
93
|
+
console.print(f"[red]Error: GitHub repository '{name}' already exists.[/red]")
|
|
94
|
+
raise typer.Exit(1)
|
|
95
|
+
|
|
96
|
+
# Get GitHub username for README
|
|
97
|
+
username = get_github_username() or "<username>"
|
|
98
|
+
|
|
99
|
+
# Create the repository
|
|
100
|
+
console.print(f"Creating agent-resources repository at {target_path}...")
|
|
101
|
+
create_agent_resources_repo(target_path, username)
|
|
102
|
+
|
|
103
|
+
# Initialize git
|
|
104
|
+
if init_git(target_path):
|
|
105
|
+
console.print("[green]Initialized git repository[/green]")
|
|
106
|
+
else:
|
|
107
|
+
console.print("[yellow]Warning: Could not initialize git repository[/yellow]")
|
|
108
|
+
|
|
109
|
+
# Create GitHub repo if requested
|
|
110
|
+
if github:
|
|
111
|
+
console.print("Creating GitHub repository...")
|
|
112
|
+
repo_url = create_github_repo(target_path, name)
|
|
113
|
+
if repo_url:
|
|
114
|
+
console.print(f"[green]Created and pushed to {repo_url}[/green]")
|
|
115
|
+
console.print(f"\nOthers can now install your resources:")
|
|
116
|
+
console.print(f" agr add skill {username}/hello-world")
|
|
117
|
+
console.print(f" agr add command {username}/hello")
|
|
118
|
+
console.print(f" agr add agent {username}/hello-agent")
|
|
119
|
+
else:
|
|
120
|
+
console.print("[yellow]Warning: Could not create GitHub repository[/yellow]")
|
|
121
|
+
else:
|
|
122
|
+
console.print(f"\n[green]Created agent-resources repository at {target_path}[/green]")
|
|
123
|
+
console.print("\nNext steps:")
|
|
124
|
+
console.print(f" cd {target_path}")
|
|
125
|
+
console.print(" git remote add origin <your-repo-url>")
|
|
126
|
+
console.print(" git push -u origin main")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@app.command("skill")
|
|
130
|
+
def init_skill(
|
|
131
|
+
name: Annotated[
|
|
132
|
+
str,
|
|
133
|
+
typer.Argument(
|
|
134
|
+
help="Name of the skill to create",
|
|
135
|
+
metavar="NAME",
|
|
136
|
+
),
|
|
137
|
+
],
|
|
138
|
+
path: Annotated[
|
|
139
|
+
Path | None,
|
|
140
|
+
typer.Option(
|
|
141
|
+
"--path",
|
|
142
|
+
"-p",
|
|
143
|
+
help="Custom path (default: ./.claude/skills/<name>/)",
|
|
144
|
+
),
|
|
145
|
+
] = None,
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Create a new skill scaffold.
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
agr init skill my-skill
|
|
151
|
+
agr init skill code-reviewer --path ./custom/path/
|
|
152
|
+
"""
|
|
153
|
+
target_path = path or (Path.cwd() / ".claude" / "skills" / name)
|
|
154
|
+
skill_file = target_path / "SKILL.md"
|
|
155
|
+
|
|
156
|
+
if skill_file.exists():
|
|
157
|
+
console.print(f"[red]Error: Skill already exists at {skill_file}[/red]")
|
|
158
|
+
raise typer.Exit(1)
|
|
159
|
+
|
|
160
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
161
|
+
|
|
162
|
+
skill_content = f"""\
|
|
163
|
+
---
|
|
164
|
+
name: {name}
|
|
165
|
+
description: Description of what this skill does
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
# {name.replace('-', ' ').title()} Skill
|
|
169
|
+
|
|
170
|
+
Describe what this skill does and when Claude should apply it.
|
|
171
|
+
|
|
172
|
+
## When to Use
|
|
173
|
+
|
|
174
|
+
Describe the situations when this skill should be applied.
|
|
175
|
+
|
|
176
|
+
## Instructions
|
|
177
|
+
|
|
178
|
+
Provide specific instructions for Claude to follow.
|
|
179
|
+
"""
|
|
180
|
+
skill_file.write_text(skill_content)
|
|
181
|
+
console.print(f"[green]Created skill at {skill_file}[/green]")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@app.command("command")
|
|
185
|
+
def init_command(
|
|
186
|
+
name: Annotated[
|
|
187
|
+
str,
|
|
188
|
+
typer.Argument(
|
|
189
|
+
help="Name of the command to create (without leading slash)",
|
|
190
|
+
metavar="NAME",
|
|
191
|
+
),
|
|
192
|
+
],
|
|
193
|
+
path: Annotated[
|
|
194
|
+
Path | None,
|
|
195
|
+
typer.Option(
|
|
196
|
+
"--path",
|
|
197
|
+
"-p",
|
|
198
|
+
help="Custom path (default: ./.claude/commands/<name>.md)",
|
|
199
|
+
),
|
|
200
|
+
] = None,
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Create a new slash command scaffold.
|
|
203
|
+
|
|
204
|
+
Examples:
|
|
205
|
+
agr init command my-command
|
|
206
|
+
agr init command deploy --path ./custom/path/
|
|
207
|
+
"""
|
|
208
|
+
target_path = path or (Path.cwd() / ".claude" / "commands")
|
|
209
|
+
command_file = target_path / f"{name}.md"
|
|
210
|
+
|
|
211
|
+
if command_file.exists():
|
|
212
|
+
console.print(f"[red]Error: Command already exists at {command_file}[/red]")
|
|
213
|
+
raise typer.Exit(1)
|
|
214
|
+
|
|
215
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
216
|
+
|
|
217
|
+
command_content = f"""\
|
|
218
|
+
---
|
|
219
|
+
description: Description of /{name} command
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
When the user runs /{name}, do the following:
|
|
223
|
+
|
|
224
|
+
1. First step
|
|
225
|
+
2. Second step
|
|
226
|
+
3. Third step
|
|
227
|
+
|
|
228
|
+
Provide clear, actionable instructions for what Claude should do.
|
|
229
|
+
"""
|
|
230
|
+
command_file.write_text(command_content)
|
|
231
|
+
console.print(f"[green]Created command at {command_file}[/green]")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@app.command("agent")
|
|
235
|
+
def init_agent(
|
|
236
|
+
name: Annotated[
|
|
237
|
+
str,
|
|
238
|
+
typer.Argument(
|
|
239
|
+
help="Name of the agent to create",
|
|
240
|
+
metavar="NAME",
|
|
241
|
+
),
|
|
242
|
+
],
|
|
243
|
+
path: Annotated[
|
|
244
|
+
Path | None,
|
|
245
|
+
typer.Option(
|
|
246
|
+
"--path",
|
|
247
|
+
"-p",
|
|
248
|
+
help="Custom path (default: ./.claude/agents/<name>.md)",
|
|
249
|
+
),
|
|
250
|
+
] = None,
|
|
251
|
+
) -> None:
|
|
252
|
+
"""Create a new sub-agent scaffold.
|
|
253
|
+
|
|
254
|
+
Examples:
|
|
255
|
+
agr init agent my-agent
|
|
256
|
+
agr init agent test-writer --path ./custom/path/
|
|
257
|
+
"""
|
|
258
|
+
target_path = path or (Path.cwd() / ".claude" / "agents")
|
|
259
|
+
agent_file = target_path / f"{name}.md"
|
|
260
|
+
|
|
261
|
+
if agent_file.exists():
|
|
262
|
+
console.print(f"[red]Error: Agent already exists at {agent_file}[/red]")
|
|
263
|
+
raise typer.Exit(1)
|
|
264
|
+
|
|
265
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
266
|
+
|
|
267
|
+
agent_content = f"""\
|
|
268
|
+
---
|
|
269
|
+
description: Description of the {name} sub-agent
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
You are a specialized sub-agent for {name.replace('-', ' ')}.
|
|
273
|
+
|
|
274
|
+
## Purpose
|
|
275
|
+
|
|
276
|
+
Describe the specific purpose and capabilities of this agent.
|
|
277
|
+
|
|
278
|
+
## Instructions
|
|
279
|
+
|
|
280
|
+
When invoked, you should:
|
|
281
|
+
|
|
282
|
+
1. First action
|
|
283
|
+
2. Second action
|
|
284
|
+
3. Third action
|
|
285
|
+
|
|
286
|
+
## Constraints
|
|
287
|
+
|
|
288
|
+
- Constraint 1
|
|
289
|
+
- Constraint 2
|
|
290
|
+
"""
|
|
291
|
+
agent_file.write_text(agent_content)
|
|
292
|
+
console.print(f"[green]Created agent at {agent_file}[/green]")
|
agr/cli/main.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Main CLI entrypoint for agr."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from agr.cli.add import app as add_app
|
|
6
|
+
from agr.cli.init import app as init_app
|
|
7
|
+
from agr.cli.remove import app as remove_app
|
|
8
|
+
from agr.cli.sync import sync as sync_command
|
|
9
|
+
from agr.cli.update import app as update_app
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(
|
|
12
|
+
name="agr",
|
|
13
|
+
help="Agent Resources - Install and create Claude Code skills, commands, and agents.",
|
|
14
|
+
add_completion=False,
|
|
15
|
+
no_args_is_help=True,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Register subcommands
|
|
19
|
+
app.add_typer(add_app, name="add")
|
|
20
|
+
app.add_typer(init_app, name="init")
|
|
21
|
+
app.add_typer(remove_app, name="remove")
|
|
22
|
+
app.add_typer(update_app, name="update")
|
|
23
|
+
|
|
24
|
+
# Register sync command
|
|
25
|
+
app.command()(sync_command)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main() -> None:
|
|
29
|
+
"""Main entry point."""
|
|
30
|
+
app()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
main()
|
agr/cli/remove.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Remove subcommand for agr - delete local resources."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, List, Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from agr.cli.common import handle_remove_bundle, handle_remove_resource, handle_remove_unified
|
|
9
|
+
from agr.fetcher import ResourceType
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
# Deprecated subcommand names
|
|
14
|
+
DEPRECATED_SUBCOMMANDS = {"skill", "command", "agent", "bundle"}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def extract_type_from_args(
|
|
18
|
+
args: list[str] | None, explicit_type: str | None
|
|
19
|
+
) -> tuple[list[str], str | None]:
|
|
20
|
+
"""
|
|
21
|
+
Extract --type/-t option from args list if present.
|
|
22
|
+
|
|
23
|
+
When --type or -t appears after the resource name, Typer captures it
|
|
24
|
+
as part of the variadic args list. This function extracts it.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
args: The argument list (may contain --type/-t)
|
|
28
|
+
explicit_type: The resource_type value from Typer (may be None if type was in args)
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Tuple of (cleaned_args, resource_type)
|
|
32
|
+
"""
|
|
33
|
+
if not args or explicit_type is not None:
|
|
34
|
+
return args or [], explicit_type
|
|
35
|
+
|
|
36
|
+
cleaned_args = []
|
|
37
|
+
resource_type = None
|
|
38
|
+
i = 0
|
|
39
|
+
while i < len(args):
|
|
40
|
+
if args[i] in ("--type", "-t") and i + 1 < len(args):
|
|
41
|
+
resource_type = args[i + 1]
|
|
42
|
+
i += 2 # Skip both --type and its value
|
|
43
|
+
else:
|
|
44
|
+
cleaned_args.append(args[i])
|
|
45
|
+
i += 1
|
|
46
|
+
|
|
47
|
+
return cleaned_args, resource_type
|
|
48
|
+
|
|
49
|
+
app = typer.Typer(
|
|
50
|
+
help="Remove skills, commands, or agents.",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.callback(invoke_without_command=True)
|
|
55
|
+
def remove_unified(
|
|
56
|
+
ctx: typer.Context,
|
|
57
|
+
args: Annotated[
|
|
58
|
+
Optional[List[str]],
|
|
59
|
+
typer.Argument(help="Name of the resource to remove"),
|
|
60
|
+
] = None,
|
|
61
|
+
resource_type: Annotated[
|
|
62
|
+
Optional[str],
|
|
63
|
+
typer.Option(
|
|
64
|
+
"--type",
|
|
65
|
+
"-t",
|
|
66
|
+
help="Explicit resource type: skill, command, agent, or bundle",
|
|
67
|
+
),
|
|
68
|
+
] = None,
|
|
69
|
+
global_install: Annotated[
|
|
70
|
+
bool,
|
|
71
|
+
typer.Option(
|
|
72
|
+
"--global",
|
|
73
|
+
"-g",
|
|
74
|
+
help="Remove from ~/.claude/ instead of ./.claude/",
|
|
75
|
+
),
|
|
76
|
+
] = False,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Remove a resource from the local installation with auto-detection.
|
|
79
|
+
|
|
80
|
+
Auto-detects the resource type (skill, command, agent) from local files.
|
|
81
|
+
Use --type to explicitly specify when a name exists in multiple types.
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
agr remove hello-world
|
|
85
|
+
agr remove hello-world --type skill
|
|
86
|
+
agr remove hello-world --global
|
|
87
|
+
"""
|
|
88
|
+
# Extract --type/-t from args if it was captured there (happens when type comes after name)
|
|
89
|
+
cleaned_args, resource_type = extract_type_from_args(args, resource_type)
|
|
90
|
+
|
|
91
|
+
if not cleaned_args:
|
|
92
|
+
console.print(ctx.get_help())
|
|
93
|
+
raise typer.Exit(0)
|
|
94
|
+
|
|
95
|
+
first_arg = cleaned_args[0]
|
|
96
|
+
|
|
97
|
+
# Handle deprecated subcommand syntax: agr remove skill <name>
|
|
98
|
+
if first_arg in DEPRECATED_SUBCOMMANDS:
|
|
99
|
+
if len(cleaned_args) < 2:
|
|
100
|
+
console.print(f"[red]Error: Missing resource name after '{first_arg}'.[/red]")
|
|
101
|
+
raise typer.Exit(1)
|
|
102
|
+
|
|
103
|
+
name = cleaned_args[1]
|
|
104
|
+
if first_arg == "bundle":
|
|
105
|
+
console.print(
|
|
106
|
+
f"[yellow]Warning: 'agr remove bundle' is deprecated. "
|
|
107
|
+
f"Use 'agr remove {name} --type bundle' instead.[/yellow]"
|
|
108
|
+
)
|
|
109
|
+
handle_remove_bundle(name, global_install)
|
|
110
|
+
else:
|
|
111
|
+
console.print(
|
|
112
|
+
f"[yellow]Warning: 'agr remove {first_arg}' is deprecated. "
|
|
113
|
+
f"Use 'agr remove {name}' instead.[/yellow]"
|
|
114
|
+
)
|
|
115
|
+
if first_arg == "skill":
|
|
116
|
+
handle_remove_resource(name, ResourceType.SKILL, "skills", global_install)
|
|
117
|
+
elif first_arg == "command":
|
|
118
|
+
handle_remove_resource(name, ResourceType.COMMAND, "commands", global_install)
|
|
119
|
+
elif first_arg == "agent":
|
|
120
|
+
handle_remove_resource(name, ResourceType.AGENT, "agents", global_install)
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
# Normal unified remove: agr remove <name>
|
|
124
|
+
name = first_arg
|
|
125
|
+
handle_remove_unified(name, resource_type, global_install)
|