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.
@@ -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,5 @@
1
+ """Allow running as: python -m agent_skill_manager"""
2
+ from agent_skill_manager.controllers.cli import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,4 @@
1
+ """Configuration layer: product registry and platform constants.
2
+
3
+ Mirrors the ``config/`` layer of MoneyPrinterTurbo's ``app/`` layout.
4
+ """
@@ -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,4 @@
1
+ """Controllers layer: user-facing interfaces (CLI).
2
+
3
+ Mirrors the ``controllers/`` layer of MoneyPrinterTurbo's ``app/`` layout.
4
+ """
@@ -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
@@ -0,0 +1,4 @@
1
+ """Services layer: business logic (sync, audit).
2
+
3
+ Mirrors the ``services/`` layer of MoneyPrinterTurbo's ``app/`` layout.
4
+ """