cn-skill-sync 0.7.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.
- agent_skill_manager/__init__.py +12 -0
- agent_skill_manager/__main__.py +5 -0
- agent_skill_manager/config/__init__.py +4 -0
- agent_skill_manager/config/products.py +161 -0
- agent_skill_manager/controllers/__init__.py +4 -0
- agent_skill_manager/controllers/cli.py +258 -0
- agent_skill_manager/models/__init__.py +11 -0
- agent_skill_manager/models/product.py +25 -0
- agent_skill_manager/models/report.py +34 -0
- agent_skill_manager/services/__init__.py +4 -0
- agent_skill_manager/services/audit.py +361 -0
- agent_skill_manager/services/sync.py +722 -0
- agent_skill_manager/utils/__init__.py +4 -0
- agent_skill_manager/utils/filesystem.py +161 -0
- cn_skill_sync-0.7.0.dist-info/METADATA +267 -0
- cn_skill_sync-0.7.0.dist-info/RECORD +20 -0
- cn_skill_sync-0.7.0.dist-info/WHEEL +5 -0
- cn_skill_sync-0.7.0.dist-info/entry_points.txt +2 -0
- cn_skill_sync-0.7.0.dist-info/licenses/LICENSE +21 -0
- cn_skill_sync-0.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Agent Skill Manager - Cross-platform skill management for domestic AI agent products.
|
|
2
|
+
|
|
3
|
+
Layered package layout (mirrors MoneyPrinterTurbo's ``app/`` structure):
|
|
4
|
+
config/ product registry & platform constants
|
|
5
|
+
controllers/ user-facing CLI
|
|
6
|
+
models/ typed data shapes (TypedDicts)
|
|
7
|
+
services/ business logic (sync, audit)
|
|
8
|
+
utils/ cross-platform filesystem helpers
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.7.0"
|
|
12
|
+
__all__ = ["config", "controllers", "models", "services", "utils"]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Product definitions for supported AI agent platforms."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ..models.product import ProductSpec
|
|
10
|
+
|
|
11
|
+
HOME = Path.home()
|
|
12
|
+
LOCALAPPDATA = Path(os.environ.get("LOCALAPPDATA", HOME / "AppData" / "Local"))
|
|
13
|
+
IS_WINDOWS = platform.system() == "Windows"
|
|
14
|
+
IS_MACOS = platform.system() == "Darwin"
|
|
15
|
+
|
|
16
|
+
# Central repository - the authoritative source for all skills
|
|
17
|
+
CENTRAL_DIR = HOME / ".agents" / "skills"
|
|
18
|
+
|
|
19
|
+
PRODUCTS: list[ProductSpec] = [
|
|
20
|
+
{
|
|
21
|
+
"name": "AutoClaw",
|
|
22
|
+
"short": "autoclaw",
|
|
23
|
+
"macos_path": HOME / ".openclaw" / "skills",
|
|
24
|
+
"windows_path": HOME / ".openclaw" / "skills",
|
|
25
|
+
"sync_method": "symlink",
|
|
26
|
+
"note": "AutoClaw also scans ~/.openclaw-autoclaw/skills/ and ~/.agents/skills/",
|
|
27
|
+
"extra_dirs_macos": [HOME / ".openclaw-autoclaw" / "skills"],
|
|
28
|
+
"extra_dirs_windows": [HOME / ".openclaw-autoclaw" / "skills"],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "Kimi",
|
|
32
|
+
"short": "kimi",
|
|
33
|
+
"macos_path": HOME / ".config" / "agents" / "skills",
|
|
34
|
+
"windows_path": HOME / ".config" / "agents" / "skills",
|
|
35
|
+
"sync_method": "symlink",
|
|
36
|
+
"note": "Kimi also scans ~/.kimi-code/skills/",
|
|
37
|
+
"extra_dirs_macos": [HOME / ".kimi-code" / "skills"],
|
|
38
|
+
"extra_dirs_windows": [HOME / ".kimi-code" / "skills"],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "MiniMax Code",
|
|
42
|
+
"short": "minimax",
|
|
43
|
+
"macos_path": HOME / ".agents" / "skills",
|
|
44
|
+
"windows_path": HOME / ".agents" / "skills",
|
|
45
|
+
"sync_method": "native",
|
|
46
|
+
"note": "MiniMax Code natively scans ~/.agents/skills/, no sync needed",
|
|
47
|
+
"extra_dirs_macos": [],
|
|
48
|
+
"extra_dirs_windows": [],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "WorkBuddy",
|
|
52
|
+
"short": "workbuddy",
|
|
53
|
+
"macos_path": HOME / ".workbuddy" / "skills",
|
|
54
|
+
"windows_path": HOME / ".workbuddy" / "skills",
|
|
55
|
+
"sync_method": "symlink",
|
|
56
|
+
"note": "Enable skill in settings.json",
|
|
57
|
+
"extra_dirs_macos": [],
|
|
58
|
+
"extra_dirs_windows": [],
|
|
59
|
+
"settings_file": HOME / ".workbuddy" / "settings.json",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "Trae",
|
|
63
|
+
"short": "trae",
|
|
64
|
+
"macos_path": HOME / ".trae" / "skills",
|
|
65
|
+
"windows_path": HOME / ".trae" / "skills",
|
|
66
|
+
"sync_method": "symlink",
|
|
67
|
+
"note": "Trae also supports project-level .trae/skills/ and .agents/skills/",
|
|
68
|
+
"extra_dirs_macos": [],
|
|
69
|
+
"extra_dirs_windows": [],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "DuMate (Baidu)",
|
|
73
|
+
"short": "dumate",
|
|
74
|
+
"macos_path": None,
|
|
75
|
+
"windows_path": None,
|
|
76
|
+
"sync_method": "pack",
|
|
77
|
+
"note": "DuMate manages skills via App; use pack command to generate .zip",
|
|
78
|
+
"extra_dirs_macos": [],
|
|
79
|
+
"extra_dirs_windows": [],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "CodeBuddy (Tencent)",
|
|
83
|
+
"short": "codebuddy",
|
|
84
|
+
"macos_path": HOME / ".codebuddy" / "skills",
|
|
85
|
+
"windows_path": HOME / ".codebuddy" / "skills",
|
|
86
|
+
"sync_method": "symlink",
|
|
87
|
+
"note": "CodeBuddy CLI also scans ~/.agents/skills/; settings in ~/.codebuddy/settings.json",
|
|
88
|
+
"extra_dirs_macos": [],
|
|
89
|
+
"extra_dirs_windows": [],
|
|
90
|
+
"settings_file": HOME / ".codebuddy" / "settings.json",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "Comate / Wenxin Kuaima (Baidu)",
|
|
94
|
+
"short": "comate",
|
|
95
|
+
"macos_path": HOME / ".comate" / "skills",
|
|
96
|
+
"windows_path": HOME / ".comate" / "skills",
|
|
97
|
+
"sync_method": "symlink",
|
|
98
|
+
"note": "Comate auto-loads skills from ~/.comate/skills/ on startup",
|
|
99
|
+
"extra_dirs_macos": [],
|
|
100
|
+
"extra_dirs_windows": [],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "Qoder / Tongyi Lingma (Alibaba)",
|
|
104
|
+
"short": "qoder",
|
|
105
|
+
"macos_path": HOME / ".qoderwork" / "skills",
|
|
106
|
+
"windows_path": HOME / ".qoderwork" / "skills",
|
|
107
|
+
"sync_method": "symlink",
|
|
108
|
+
"note": "QoderWork CN stores skills in ~/.qoderwork/skills/",
|
|
109
|
+
"extra_dirs_macos": [],
|
|
110
|
+
"extra_dirs_windows": [],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "QwenWork / Qianwen Office (Alibaba)",
|
|
114
|
+
"short": "qwenwork",
|
|
115
|
+
"macos_path": HOME / ".qwenworkcn" / "skills",
|
|
116
|
+
"windows_path": HOME / ".qwenworkcn" / "skills",
|
|
117
|
+
"sync_method": "symlink",
|
|
118
|
+
"note": "QwenWork desktop scans ~/.qwenworkcn/skills/; frontmatter needs name+version+description+description_zh",
|
|
119
|
+
"extra_dirs_macos": [],
|
|
120
|
+
"extra_dirs_windows": [],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "DoubaoWork (ByteDance)",
|
|
124
|
+
"short": "doubaowork",
|
|
125
|
+
"macos_path": HOME / ".super_doubao" / "super-doubao-runtime" / "workspace" / ".user_skills",
|
|
126
|
+
"windows_path": LOCALAPPDATA / "DoubaoWork" / "User Data" / "Default" / ".doubaowork" / "agent_mode" / "workspace" / ".user_skills",
|
|
127
|
+
"sync_method": "symlink",
|
|
128
|
+
"note": "DoubaoWork user skills live in workspace/.user_skills; system skills in workspace/.skills",
|
|
129
|
+
"extra_dirs_macos": [],
|
|
130
|
+
"extra_dirs_windows": [],
|
|
131
|
+
},
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def get_product_path(product: ProductSpec) -> Path | None:
|
|
136
|
+
"""Get the primary skill directory for a product on the current platform."""
|
|
137
|
+
if IS_WINDOWS:
|
|
138
|
+
return product.get("windows_path")
|
|
139
|
+
else:
|
|
140
|
+
return product.get("macos_path")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def get_all_product_dirs(product: ProductSpec) -> list[Path]:
|
|
144
|
+
"""Get all skill directories for a product on the current platform."""
|
|
145
|
+
primary = get_product_path(product)
|
|
146
|
+
dirs = []
|
|
147
|
+
if primary:
|
|
148
|
+
dirs.append(primary)
|
|
149
|
+
if IS_WINDOWS:
|
|
150
|
+
dirs.extend(product.get("extra_dirs_windows", []))
|
|
151
|
+
else:
|
|
152
|
+
dirs.extend(product.get("extra_dirs_macos", []))
|
|
153
|
+
return dirs
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_product_by_short(name: str) -> ProductSpec | None:
|
|
157
|
+
"""Find a product by its short name."""
|
|
158
|
+
for p in PRODUCTS:
|
|
159
|
+
if p["short"] == name:
|
|
160
|
+
return p
|
|
161
|
+
return None
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""CLI entry point for agent-skill-manager."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import platform
|
|
5
|
+
|
|
6
|
+
from ..config.products import PRODUCTS, CENTRAL_DIR, get_product_path, get_all_product_dirs
|
|
7
|
+
from ..utils.filesystem import is_symlink_or_junction, read_skill_metadata
|
|
8
|
+
from ..services.audit import analyze_skill_dir
|
|
9
|
+
from ..services.sync import FOOTER
|
|
10
|
+
from ..services.sync import (
|
|
11
|
+
list_skills,
|
|
12
|
+
get_status,
|
|
13
|
+
sync_skill,
|
|
14
|
+
install_skill,
|
|
15
|
+
remove_skill,
|
|
16
|
+
pack_skill,
|
|
17
|
+
adopt_from_platform,
|
|
18
|
+
audit_skill,
|
|
19
|
+
audit_all,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _print_products():
|
|
24
|
+
"""Print all supported products and their paths."""
|
|
25
|
+
print(f"\n{'='*60}")
|
|
26
|
+
print(f"Supported Products ({len(PRODUCTS)})")
|
|
27
|
+
print(f"{'='*60}")
|
|
28
|
+
print(f"Platform: {platform.system()}")
|
|
29
|
+
print(f"Central repo: {CENTRAL_DIR}")
|
|
30
|
+
print(f"{'='*60}\n")
|
|
31
|
+
|
|
32
|
+
for p in PRODUCTS:
|
|
33
|
+
print(f" [{p['short']}] {p['name']}")
|
|
34
|
+
if p["sync_method"] == "native":
|
|
35
|
+
path = get_product_path(p)
|
|
36
|
+
print(f" Path: {path} (native, no sync needed)")
|
|
37
|
+
elif p["sync_method"] == "pack":
|
|
38
|
+
print(f" Path: App-managed (use 'pack' command)")
|
|
39
|
+
else:
|
|
40
|
+
primary = get_product_path(p)
|
|
41
|
+
if primary:
|
|
42
|
+
exists = "[ok]" if primary.exists() else "[--]"
|
|
43
|
+
print(f" Path: {primary} {exists}")
|
|
44
|
+
extra_dirs = get_all_product_dirs(p)
|
|
45
|
+
for d in extra_dirs[1:]:
|
|
46
|
+
exists = "[ok]" if d.exists() else "[--]"
|
|
47
|
+
print(f" Alt: {d} {exists}")
|
|
48
|
+
if p.get("note"):
|
|
49
|
+
print(f" Note: {p['note']}")
|
|
50
|
+
print()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _print_list():
|
|
54
|
+
"""List all skills in the central repository."""
|
|
55
|
+
skills = list_skills()
|
|
56
|
+
if not skills:
|
|
57
|
+
if not CENTRAL_DIR.exists():
|
|
58
|
+
print(f"Central repository not found: {CENTRAL_DIR}")
|
|
59
|
+
print("Create it by installing a skill: askill install <path>")
|
|
60
|
+
else:
|
|
61
|
+
print(f"No skills found in {CENTRAL_DIR}")
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
print(f"\nSkills in central repository ({CENTRAL_DIR}):")
|
|
65
|
+
print(f"{'-'*50}")
|
|
66
|
+
verdict_icon = {"safe": "OK", "caution": "CAUTION", "risky": "RISKY", "dangerous": "DANGEROUS"}
|
|
67
|
+
for s in skills:
|
|
68
|
+
meta = read_skill_metadata(s)
|
|
69
|
+
report = analyze_skill_dir(s)
|
|
70
|
+
print(f" {s.name} [score {report['score']}/100 grade {report['grade']} {verdict_icon[report['verdict']]}]")
|
|
71
|
+
if meta.get("description"):
|
|
72
|
+
desc = meta["description"][:80]
|
|
73
|
+
print(f" -> {desc}...")
|
|
74
|
+
print()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _print_status(skill_name=None):
|
|
78
|
+
"""Print installation status of skills across all products."""
|
|
79
|
+
results = get_status(skill_name)
|
|
80
|
+
if not results:
|
|
81
|
+
if skill_name:
|
|
82
|
+
print(f"Skill not found: {skill_name}")
|
|
83
|
+
else:
|
|
84
|
+
print("No skills found in central repository.")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# Build table
|
|
88
|
+
product_shorts = [p["short"] for p in PRODUCTS]
|
|
89
|
+
header = f"{'Skill':<25}"
|
|
90
|
+
for ps in product_shorts:
|
|
91
|
+
header += f" {ps:<12}"
|
|
92
|
+
header += f" {'score':<13}"
|
|
93
|
+
print(f"\n{header}")
|
|
94
|
+
print(f"{'-'*(25 + len(PRODUCTS)*13 + 14)}")
|
|
95
|
+
|
|
96
|
+
# Group results by skill (skill -> {product_short: (status, method)})
|
|
97
|
+
verdict_icon = {"safe": "OK", "caution": "CAUTION", "risky": "RISKY", "dangerous": "DANGEROUS"}
|
|
98
|
+
by_skill = {}
|
|
99
|
+
order = []
|
|
100
|
+
for r in results:
|
|
101
|
+
sn = r["skill_name"]
|
|
102
|
+
if sn not in by_skill:
|
|
103
|
+
by_skill[sn] = {}
|
|
104
|
+
order.append(sn)
|
|
105
|
+
by_skill[sn][r["product_short"]] = (r["status"], r["method"])
|
|
106
|
+
|
|
107
|
+
for sn in order:
|
|
108
|
+
row = f"{sn:<25}"
|
|
109
|
+
for ps in product_shorts:
|
|
110
|
+
if ps in by_skill[sn]:
|
|
111
|
+
status, method = by_skill[sn][ps]
|
|
112
|
+
if status == "ok":
|
|
113
|
+
cell = f"ok {method}"
|
|
114
|
+
elif status == "missing":
|
|
115
|
+
cell = "--"
|
|
116
|
+
elif status == "manual":
|
|
117
|
+
cell = "manual"
|
|
118
|
+
elif status == "n/a":
|
|
119
|
+
cell = "n/a"
|
|
120
|
+
else:
|
|
121
|
+
cell = status
|
|
122
|
+
else:
|
|
123
|
+
cell = "--"
|
|
124
|
+
row += f" {cell:<11}"
|
|
125
|
+
# Security audit score for this skill
|
|
126
|
+
skill_dir = CENTRAL_DIR / sn
|
|
127
|
+
if skill_dir.exists():
|
|
128
|
+
report = analyze_skill_dir(skill_dir)
|
|
129
|
+
row += f" {report['score']}/{report['grade']} {verdict_icon[report['verdict']]:<7}"
|
|
130
|
+
else:
|
|
131
|
+
row += f" {'n/a':<13}"
|
|
132
|
+
print(row)
|
|
133
|
+
print(f" -- {FOOTER}")
|
|
134
|
+
print()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _print_sync(skill_name=None):
|
|
138
|
+
"""Sync skills and print results."""
|
|
139
|
+
sync_skill(skill_name, verbose=True)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _print_install(source, sync=False, audit=False):
|
|
143
|
+
"""Install a skill and print results."""
|
|
144
|
+
install_skill(source, sync=sync, audit=audit, verbose=True)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _print_remove(skill_name):
|
|
148
|
+
"""Remove a skill and print results."""
|
|
149
|
+
remove_skill(skill_name, verbose=True)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _print_pack(skill_name):
|
|
153
|
+
"""Pack a skill and print results."""
|
|
154
|
+
pack_skill(skill_name, verbose=True)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _print_adopt(platform_short, skill_name=None):
|
|
158
|
+
"""Adopt skills from one platform to all others."""
|
|
159
|
+
adopt_from_platform(platform_short, skill_name, verbose=True)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _print_audit(skill_name=None):
|
|
163
|
+
"""Audit one or all skills and print reports."""
|
|
164
|
+
if skill_name:
|
|
165
|
+
audit_skill(skill_name, verbose=True)
|
|
166
|
+
else:
|
|
167
|
+
audit_all(verbose=True)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
USAGE = """\
|
|
172
|
+
Agent Skill Manager - Cross-platform skill management for domestic AI agent products.
|
|
173
|
+
|
|
174
|
+
Supports: AutoClaw, Kimi, MiniMax Code, WorkBuddy, Trae, DuMate.
|
|
175
|
+
|
|
176
|
+
Usage:
|
|
177
|
+
askill status [skill-name] Show installation status across products
|
|
178
|
+
askill sync [skill-name] Sync skill(s) to all products
|
|
179
|
+
askill list List skills in central repository
|
|
180
|
+
askill install [--sync] <path-or-url> Install a skill, optionally sync to all
|
|
181
|
+
askill remove <skill-name> Remove a skill from all products
|
|
182
|
+
askill pack <skill-name> Package a skill as .zip for DuMate
|
|
183
|
+
askill adopt <platform> [skill] Adopt skills from one platform to all others
|
|
184
|
+
askill audit [skill-name] Security audit of skill(s) in central repo
|
|
185
|
+
askill products List all supported products
|
|
186
|
+
askill version Show version
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def main():
|
|
191
|
+
"""CLI entry point."""
|
|
192
|
+
if len(sys.argv) < 2:
|
|
193
|
+
print(USAGE)
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
command = sys.argv[1].lower()
|
|
197
|
+
|
|
198
|
+
if command == "status":
|
|
199
|
+
skill_name = sys.argv[2] if len(sys.argv) > 2 else None
|
|
200
|
+
_print_status(skill_name)
|
|
201
|
+
elif command == "sync":
|
|
202
|
+
skill_name = sys.argv[2] if len(sys.argv) > 2 else None
|
|
203
|
+
_print_sync(skill_name)
|
|
204
|
+
elif command == "list":
|
|
205
|
+
_print_list()
|
|
206
|
+
elif command == "install":
|
|
207
|
+
if len(sys.argv) < 3 or "--help" in sys.argv or "-h" in sys.argv:
|
|
208
|
+
print("Usage: askill install [--sync] [--audit] <path-or-url>")
|
|
209
|
+
print(" --sync Also sync to all products after install")
|
|
210
|
+
print(" --audit Run security audit after install")
|
|
211
|
+
return
|
|
212
|
+
do_sync = False
|
|
213
|
+
do_audit = False
|
|
214
|
+
args = sys.argv[2:]
|
|
215
|
+
if "--sync" in args:
|
|
216
|
+
do_sync = True
|
|
217
|
+
args.remove("--sync")
|
|
218
|
+
if "--audit" in args:
|
|
219
|
+
do_audit = True
|
|
220
|
+
args.remove("--audit")
|
|
221
|
+
if not args:
|
|
222
|
+
print("Usage: askill install [--sync] [--audit] <path-or-url>")
|
|
223
|
+
return
|
|
224
|
+
_print_install(args[0], sync=do_sync, audit=do_audit)
|
|
225
|
+
elif command == "remove":
|
|
226
|
+
if len(sys.argv) < 3:
|
|
227
|
+
print("Usage: askill remove <skill-name>")
|
|
228
|
+
return
|
|
229
|
+
_print_remove(sys.argv[2])
|
|
230
|
+
elif command == "pack":
|
|
231
|
+
if len(sys.argv) < 3:
|
|
232
|
+
print("Usage: askill pack <skill-name>")
|
|
233
|
+
return
|
|
234
|
+
_print_pack(skill_name=sys.argv[2])
|
|
235
|
+
elif command == "adopt":
|
|
236
|
+
if len(sys.argv) < 3:
|
|
237
|
+
print("Usage: askill adopt <platform> [skill-name]")
|
|
238
|
+
return
|
|
239
|
+
platform = sys.argv[2].lower()
|
|
240
|
+
skill = sys.argv[3] if len(sys.argv) > 3 else None
|
|
241
|
+
_print_adopt(platform, skill)
|
|
242
|
+
elif command == "audit":
|
|
243
|
+
skill_name = sys.argv[2] if len(sys.argv) > 2 else None
|
|
244
|
+
_print_audit(skill_name)
|
|
245
|
+
elif command == "products":
|
|
246
|
+
_print_products()
|
|
247
|
+
elif command == "version":
|
|
248
|
+
from .. import __version__
|
|
249
|
+
print(f"agent-skill-manager v{__version__}")
|
|
250
|
+
elif command in ("-h", "--help", "help"):
|
|
251
|
+
print(USAGE)
|
|
252
|
+
else:
|
|
253
|
+
print(f"Unknown command: {command}")
|
|
254
|
+
print(USAGE)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
if __name__ == "__main__":
|
|
258
|
+
main()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Models layer: typed data shapes for products and audit reports.
|
|
2
|
+
|
|
3
|
+
Mirrors the ``models/`` layer of MoneyPrinterTurbo's ``app/`` layout.
|
|
4
|
+
These are TypedDicts used for documentation / type-checking only; the
|
|
5
|
+
runtime code still works with plain dicts, so no behavioral change.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .product import ProductSpec
|
|
9
|
+
from .report import Finding, SkillReport, StatusEntry
|
|
10
|
+
|
|
11
|
+
__all__ = ["ProductSpec", "Finding", "SkillReport", "StatusEntry"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Typed shape of a supported product's registry entry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, TypedDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ProductSpec(TypedDict, total=False):
|
|
10
|
+
"""Static description of one supported AI agent product.
|
|
11
|
+
|
|
12
|
+
Declared ``total=False`` because some products (e.g. DuMate) omit the
|
|
13
|
+
platform-specific path keys or the optional ``settings_file`` /
|
|
14
|
+
``extra_dirs_*`` keys.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
name: str
|
|
18
|
+
short: str
|
|
19
|
+
macos_path: Optional[Path]
|
|
20
|
+
windows_path: Optional[Path]
|
|
21
|
+
sync_method: str # "symlink" | "native" | "pack"
|
|
22
|
+
note: str
|
|
23
|
+
extra_dirs_macos: list[Path]
|
|
24
|
+
extra_dirs_windows: list[Path]
|
|
25
|
+
settings_file: Path
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Typed shapes of the static security-audit report."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TypedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Finding(TypedDict):
|
|
9
|
+
severity: str # critical | high | medium | low | info
|
|
10
|
+
category: str
|
|
11
|
+
file: str
|
|
12
|
+
line: int | None
|
|
13
|
+
pattern: str
|
|
14
|
+
message: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SkillReport(TypedDict):
|
|
18
|
+
skill: str
|
|
19
|
+
path: str
|
|
20
|
+
files: int
|
|
21
|
+
total_bytes: int
|
|
22
|
+
score: int
|
|
23
|
+
grade: str # A | B | C | D | F
|
|
24
|
+
verdict: str # safe | caution | risky | dangerous
|
|
25
|
+
summary: dict # {critical, high, medium, low, info}
|
|
26
|
+
findings: list[Finding]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StatusEntry(TypedDict):
|
|
30
|
+
skill_name: str
|
|
31
|
+
product_short: str
|
|
32
|
+
product_name: str
|
|
33
|
+
status: str # ok | missing | manual | n/a
|
|
34
|
+
method: str # symlink | junction | copy | native | pack
|