pmflow-cli 0.1.1__tar.gz → 0.1.3__tar.gz
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.
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/PKG-INFO +3 -3
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/pyproject.toml +3 -3
- pmflow_cli-0.1.3/src/pmflow_cli/commands/skills.py +198 -0
- pmflow_cli-0.1.1/src/pmflow_cli/commands/skills.py +0 -100
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/.claude-plugin/plugin.json +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/.gitignore +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/README.md +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/skills/plan-space/SKILL.md +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/skills/plan-task/SKILL.md +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/skills/prd/SKILL.md +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/skills/prdfix/SKILL.md +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/skills/snapshot-space/SKILL.md +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/__init__.py +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/client.py +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/commands/__init__.py +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/commands/ai.py +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/commands/auth.py +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/commands/context.py +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/commands/issue.py +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/commands/workspace.py +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/config.py +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/main.py +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/output.py +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/skills/plan-space/SKILL.md +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/skills/plan-task/SKILL.md +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/skills/prd/SKILL.md +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/skills/prdfix/SKILL.md +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/skills/snapshot-space/SKILL.md +0 -0
- {pmflow_cli-0.1.1 → pmflow_cli-0.1.3}/src/pmflow_cli/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pmflow-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: CLI for pmflow — project management for AI-assisted development
|
|
5
5
|
Project-URL: Repository, https://github.com/zhanglaixian/pmflow
|
|
6
6
|
Author-email: zhanglaixian <zhanglaixian@zhuanzhuan.com>
|
|
@@ -10,8 +10,8 @@ Classifier: Environment :: Console
|
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
|
-
Requires-Dist: httpx>=0.27
|
|
14
|
-
Requires-Dist: pmflow-shared>=0.1.
|
|
13
|
+
Requires-Dist: httpx[socks]>=0.27
|
|
14
|
+
Requires-Dist: pmflow-shared>=0.1.2
|
|
15
15
|
Requires-Dist: rich>=13.0
|
|
16
16
|
Requires-Dist: tomli-w>=1.0
|
|
17
17
|
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pmflow-cli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
4
4
|
description = "CLI for pmflow — project management for AI-assisted development"
|
|
5
5
|
requires-python = ">=3.12"
|
|
6
6
|
readme = "README.md"
|
|
@@ -13,9 +13,9 @@ classifiers = [
|
|
|
13
13
|
"Environment :: Console",
|
|
14
14
|
]
|
|
15
15
|
dependencies = [
|
|
16
|
-
"pmflow-shared>=0.1.
|
|
16
|
+
"pmflow-shared>=0.1.2",
|
|
17
17
|
"typer>=0.12",
|
|
18
|
-
"httpx>=0.27",
|
|
18
|
+
"httpx[socks]>=0.27",
|
|
19
19
|
"rich>=13.0",
|
|
20
20
|
"tomli>=2.0; python_version < '3.11'",
|
|
21
21
|
"tomli-w>=1.0",
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Install/uninstall pmflow Claude Code skills and Codex prompts."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from pmflow_cli.output import console
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(help="Manage Claude Code skills and Codex prompts")
|
|
13
|
+
|
|
14
|
+
SKILLS_SOURCE = Path(__file__).parent.parent / "skills"
|
|
15
|
+
|
|
16
|
+
# Claude Code target
|
|
17
|
+
COMMANDS_TARGET = Path.home() / ".claude" / "commands" / "pmflow"
|
|
18
|
+
|
|
19
|
+
# Codex target
|
|
20
|
+
CODEX_TARGET = Path.home() / ".codex" / "prompts"
|
|
21
|
+
|
|
22
|
+
SKILL_NAMES = ["plan-space", "plan-task", "prd", "prdfix", "snapshot-space"]
|
|
23
|
+
|
|
24
|
+
# Argument hints for Codex prompts (derived from each skill's usage)
|
|
25
|
+
_ARGUMENT_HINTS: dict[str, str] = {
|
|
26
|
+
"plan-space": "{slug}",
|
|
27
|
+
"plan-task": "{slug}-{seq_num}",
|
|
28
|
+
"prd": "功能描述",
|
|
29
|
+
"prdfix": "{slug}-{seq_num}",
|
|
30
|
+
"snapshot-space": "{slug}",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Legacy install path (pre-v0.2), cleaned up on install/uninstall
|
|
34
|
+
_LEGACY_TARGET = Path.home() / ".claude" / "skills"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _clean_legacy():
|
|
38
|
+
"""Remove skills installed to the old ~/.claude/skills/ location."""
|
|
39
|
+
for name in SKILL_NAMES:
|
|
40
|
+
legacy = _LEGACY_TARGET / f"pmflow-{name}"
|
|
41
|
+
if legacy.exists():
|
|
42
|
+
shutil.rmtree(legacy)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _convert_to_codex_prompt(skill_md: str, name: str) -> str:
|
|
46
|
+
"""Convert a Claude Code SKILL.md to a Codex-compatible prompt.
|
|
47
|
+
|
|
48
|
+
Replaces the Claude Code frontmatter (name/description/user-invocable)
|
|
49
|
+
with Codex frontmatter (description/argument-hint), keeping the body as-is.
|
|
50
|
+
"""
|
|
51
|
+
# Split frontmatter and body
|
|
52
|
+
match = re.match(r"^---\n(.*?)\n---\n(.*)", skill_md, re.DOTALL)
|
|
53
|
+
if not match:
|
|
54
|
+
return skill_md
|
|
55
|
+
|
|
56
|
+
frontmatter_text = match.group(1)
|
|
57
|
+
body = match.group(2)
|
|
58
|
+
|
|
59
|
+
# Extract description from original frontmatter
|
|
60
|
+
desc_match = re.search(r"^description:\s*(.+)$", frontmatter_text, re.MULTILINE)
|
|
61
|
+
# Use first sentence of description (before "用法:")
|
|
62
|
+
description = desc_match.group(1).strip() if desc_match else name
|
|
63
|
+
description = re.split(r"[。.]?用法[::]", description)[0].rstrip("。. ")
|
|
64
|
+
|
|
65
|
+
hint = _ARGUMENT_HINTS.get(name, "")
|
|
66
|
+
|
|
67
|
+
lines = [
|
|
68
|
+
"---",
|
|
69
|
+
f"description: {description}",
|
|
70
|
+
]
|
|
71
|
+
if hint:
|
|
72
|
+
lines.append(f"argument-hint: {hint}")
|
|
73
|
+
lines.append("---")
|
|
74
|
+
|
|
75
|
+
return "\n".join(lines) + "\n" + body
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@app.command()
|
|
79
|
+
def install(
|
|
80
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing skills"),
|
|
81
|
+
):
|
|
82
|
+
"""Install pmflow skills into Claude Code and Codex."""
|
|
83
|
+
if not SKILLS_SOURCE.exists():
|
|
84
|
+
console.print("[red]Error:[/red] Skills source directory not found. Package may be corrupted.")
|
|
85
|
+
raise typer.Exit(1)
|
|
86
|
+
|
|
87
|
+
_clean_legacy()
|
|
88
|
+
COMMANDS_TARGET.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
CODEX_TARGET.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
|
|
91
|
+
installed_cc: list[str] = []
|
|
92
|
+
installed_codex: list[str] = []
|
|
93
|
+
skipped_cc: list[str] = []
|
|
94
|
+
skipped_codex: list[str] = []
|
|
95
|
+
|
|
96
|
+
for name in SKILL_NAMES:
|
|
97
|
+
src = SKILLS_SOURCE / name / "SKILL.md"
|
|
98
|
+
if not src.exists():
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
# --- Claude Code ---
|
|
102
|
+
cc_dst = COMMANDS_TARGET / f"{name}.md"
|
|
103
|
+
if cc_dst.exists() and not force:
|
|
104
|
+
skipped_cc.append(name)
|
|
105
|
+
else:
|
|
106
|
+
shutil.copy2(src, cc_dst)
|
|
107
|
+
installed_cc.append(name)
|
|
108
|
+
|
|
109
|
+
# --- Codex ---
|
|
110
|
+
codex_dst = CODEX_TARGET / f"pmflow-{name}.md"
|
|
111
|
+
if codex_dst.exists() and not force:
|
|
112
|
+
skipped_codex.append(name)
|
|
113
|
+
else:
|
|
114
|
+
skill_content = src.read_text(encoding="utf-8")
|
|
115
|
+
codex_content = _convert_to_codex_prompt(skill_content, name)
|
|
116
|
+
codex_dst.write_text(codex_content, encoding="utf-8")
|
|
117
|
+
installed_codex.append(name)
|
|
118
|
+
|
|
119
|
+
# Report
|
|
120
|
+
if installed_cc:
|
|
121
|
+
console.print(f"[green]Claude Code — installed {len(installed_cc)} skill(s):[/green] {', '.join(installed_cc)}")
|
|
122
|
+
if skipped_cc:
|
|
123
|
+
console.print(f"[yellow]Claude Code — skipped {len(skipped_cc)} (already exist):[/yellow] {', '.join(skipped_cc)}")
|
|
124
|
+
|
|
125
|
+
if installed_codex:
|
|
126
|
+
console.print(f"[green]Codex — installed {len(installed_codex)} prompt(s):[/green] {', '.join(installed_codex)}")
|
|
127
|
+
if skipped_codex:
|
|
128
|
+
console.print(f"[yellow]Codex — skipped {len(skipped_codex)} (already exist):[/yellow] {', '.join(skipped_codex)}")
|
|
129
|
+
|
|
130
|
+
if skipped_cc or skipped_codex:
|
|
131
|
+
console.print("Use [bold]--force[/bold] to overwrite.")
|
|
132
|
+
|
|
133
|
+
if installed_cc or installed_codex:
|
|
134
|
+
console.print(
|
|
135
|
+
"\n[dim]Claude Code: /pmflow:plan-space, /pmflow:plan-task, /pmflow:prd, /pmflow:prdfix, /pmflow:snapshot-space\n"
|
|
136
|
+
"Codex: /prompts:pmflow-plan-space, /prompts:pmflow-plan-task, /prompts:pmflow-prd, /prompts:pmflow-prdfix, /prompts:pmflow-snapshot-space[/dim]"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@app.command()
|
|
141
|
+
def uninstall():
|
|
142
|
+
"""Remove pmflow skills from Claude Code and Codex."""
|
|
143
|
+
_clean_legacy()
|
|
144
|
+
|
|
145
|
+
removed_cc: list[str] = []
|
|
146
|
+
removed_codex: list[str] = []
|
|
147
|
+
|
|
148
|
+
for name in SKILL_NAMES:
|
|
149
|
+
# Claude Code
|
|
150
|
+
cc_dst = COMMANDS_TARGET / f"{name}.md"
|
|
151
|
+
if cc_dst.exists():
|
|
152
|
+
cc_dst.unlink()
|
|
153
|
+
removed_cc.append(name)
|
|
154
|
+
|
|
155
|
+
# Codex
|
|
156
|
+
codex_dst = CODEX_TARGET / f"pmflow-{name}.md"
|
|
157
|
+
if codex_dst.exists():
|
|
158
|
+
codex_dst.unlink()
|
|
159
|
+
removed_codex.append(name)
|
|
160
|
+
|
|
161
|
+
# Remove empty directories
|
|
162
|
+
if COMMANDS_TARGET.exists() and not any(COMMANDS_TARGET.iterdir()):
|
|
163
|
+
COMMANDS_TARGET.rmdir()
|
|
164
|
+
|
|
165
|
+
if removed_cc:
|
|
166
|
+
console.print(f"[green]Claude Code — removed {len(removed_cc)} skill(s):[/green] {', '.join(removed_cc)}")
|
|
167
|
+
if removed_codex:
|
|
168
|
+
console.print(f"[green]Codex — removed {len(removed_codex)} prompt(s):[/green] {', '.join(removed_codex)}")
|
|
169
|
+
if not removed_cc and not removed_codex:
|
|
170
|
+
console.print("[dim]No pmflow skills or prompts found to remove.[/dim]")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@app.command(name="list")
|
|
174
|
+
def list_skills():
|
|
175
|
+
"""List pmflow skills and their install status."""
|
|
176
|
+
table = Table(title="pmflow Skills & Prompts")
|
|
177
|
+
table.add_column("Skill", style="bold")
|
|
178
|
+
table.add_column("Claude Code")
|
|
179
|
+
table.add_column("CC Status")
|
|
180
|
+
table.add_column("Codex")
|
|
181
|
+
table.add_column("Codex Status")
|
|
182
|
+
|
|
183
|
+
for name in SKILL_NAMES:
|
|
184
|
+
cc_dst = COMMANDS_TARGET / f"{name}.md"
|
|
185
|
+
cc_status = "[green]installed[/green]" if cc_dst.exists() else "[dim]not installed[/dim]"
|
|
186
|
+
|
|
187
|
+
codex_dst = CODEX_TARGET / f"pmflow-{name}.md"
|
|
188
|
+
codex_status = "[green]installed[/green]" if codex_dst.exists() else "[dim]not installed[/dim]"
|
|
189
|
+
|
|
190
|
+
table.add_row(
|
|
191
|
+
name,
|
|
192
|
+
f"/pmflow:{name}",
|
|
193
|
+
cc_status,
|
|
194
|
+
f"/prompts:pmflow-{name}",
|
|
195
|
+
codex_status,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
console.print(table)
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
"""Install/uninstall pmflow Claude Code skills to ~/.claude/commands/pmflow/."""
|
|
2
|
-
|
|
3
|
-
import shutil
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
import typer
|
|
7
|
-
from rich.table import Table
|
|
8
|
-
|
|
9
|
-
from pmflow_cli.output import console
|
|
10
|
-
|
|
11
|
-
app = typer.Typer(help="Manage Claude Code skills")
|
|
12
|
-
|
|
13
|
-
SKILLS_SOURCE = Path(__file__).parent.parent / "skills"
|
|
14
|
-
COMMANDS_TARGET = Path.home() / ".claude" / "commands" / "pmflow"
|
|
15
|
-
SKILL_NAMES = ["plan-space", "plan-task", "prd", "prdfix", "snapshot-space"]
|
|
16
|
-
|
|
17
|
-
# Legacy install path (pre-v0.2), cleaned up on install/uninstall
|
|
18
|
-
_LEGACY_TARGET = Path.home() / ".claude" / "skills"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _clean_legacy():
|
|
22
|
-
"""Remove skills installed to the old ~/.claude/skills/ location."""
|
|
23
|
-
for name in SKILL_NAMES:
|
|
24
|
-
legacy = _LEGACY_TARGET / f"pmflow-{name}"
|
|
25
|
-
if legacy.exists():
|
|
26
|
-
shutil.rmtree(legacy)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@app.command()
|
|
30
|
-
def install(
|
|
31
|
-
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing skills"),
|
|
32
|
-
):
|
|
33
|
-
"""Install pmflow skills into ~/.claude/commands/pmflow/ for Claude Code."""
|
|
34
|
-
if not SKILLS_SOURCE.exists():
|
|
35
|
-
console.print("[red]Error:[/red] Skills source directory not found. Package may be corrupted.")
|
|
36
|
-
raise typer.Exit(1)
|
|
37
|
-
|
|
38
|
-
_clean_legacy()
|
|
39
|
-
COMMANDS_TARGET.mkdir(parents=True, exist_ok=True)
|
|
40
|
-
|
|
41
|
-
installed = []
|
|
42
|
-
skipped = []
|
|
43
|
-
|
|
44
|
-
for name in SKILL_NAMES:
|
|
45
|
-
src = SKILLS_SOURCE / name / "SKILL.md"
|
|
46
|
-
dst = COMMANDS_TARGET / f"{name}.md"
|
|
47
|
-
|
|
48
|
-
if dst.exists() and not force:
|
|
49
|
-
skipped.append(name)
|
|
50
|
-
continue
|
|
51
|
-
|
|
52
|
-
shutil.copy2(src, dst)
|
|
53
|
-
installed.append(name)
|
|
54
|
-
|
|
55
|
-
if installed:
|
|
56
|
-
console.print(f"[green]Installed {len(installed)} skill(s):[/green] {', '.join(installed)}")
|
|
57
|
-
if skipped:
|
|
58
|
-
console.print(f"[yellow]Skipped {len(skipped)} (already exist):[/yellow] {', '.join(skipped)}")
|
|
59
|
-
console.print("Use [bold]--force[/bold] to overwrite.")
|
|
60
|
-
|
|
61
|
-
if installed:
|
|
62
|
-
console.print("\n[dim]Skills are now available as /pmflow:plan-space, /pmflow:plan-task, /pmflow:prd, /pmflow:prdfix, /pmflow:snapshot-space in Claude Code.[/dim]")
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@app.command()
|
|
66
|
-
def uninstall():
|
|
67
|
-
"""Remove pmflow skills from ~/.claude/commands/pmflow/."""
|
|
68
|
-
_clean_legacy()
|
|
69
|
-
|
|
70
|
-
removed = []
|
|
71
|
-
for name in SKILL_NAMES:
|
|
72
|
-
dst = COMMANDS_TARGET / f"{name}.md"
|
|
73
|
-
if dst.exists():
|
|
74
|
-
dst.unlink()
|
|
75
|
-
removed.append(name)
|
|
76
|
-
|
|
77
|
-
# Remove the pmflow directory if empty
|
|
78
|
-
if COMMANDS_TARGET.exists() and not any(COMMANDS_TARGET.iterdir()):
|
|
79
|
-
COMMANDS_TARGET.rmdir()
|
|
80
|
-
|
|
81
|
-
if removed:
|
|
82
|
-
console.print(f"[green]Removed {len(removed)} skill(s):[/green] {', '.join(removed)}")
|
|
83
|
-
else:
|
|
84
|
-
console.print("[dim]No pmflow skills found to remove.[/dim]")
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@app.command(name="list")
|
|
88
|
-
def list_skills():
|
|
89
|
-
"""List pmflow skills and their install status."""
|
|
90
|
-
table = Table(title="pmflow Claude Code Skills")
|
|
91
|
-
table.add_column("Skill", style="bold")
|
|
92
|
-
table.add_column("Command")
|
|
93
|
-
table.add_column("Status")
|
|
94
|
-
|
|
95
|
-
for name in SKILL_NAMES:
|
|
96
|
-
dst = COMMANDS_TARGET / f"{name}.md"
|
|
97
|
-
status = "[green]installed[/green]" if dst.exists() else "[dim]not installed[/dim]"
|
|
98
|
-
table.add_row(name, f"/pmflow:{name}", status)
|
|
99
|
-
|
|
100
|
-
console.print(table)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|