metame-cli 1.5.11 → 1.5.12
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.
- package/index.js +64 -7
- package/package.json +3 -2
- package/scripts/daemon-agent-commands.js +6 -2
- package/scripts/daemon-bridges.js +23 -9
- package/scripts/daemon-claude-engine.js +87 -28
- package/scripts/daemon-command-router.js +16 -0
- package/scripts/daemon-command-session-route.js +3 -1
- package/scripts/daemon-engine-runtime.js +1 -5
- package/scripts/daemon-message-pipeline.js +113 -44
- package/scripts/daemon-reactive-lifecycle.js +405 -9
- package/scripts/daemon-session-commands.js +3 -2
- package/scripts/daemon-session-store.js +82 -27
- package/scripts/daemon-team-dispatch.js +21 -5
- package/scripts/daemon-utils.js +3 -1
- package/scripts/daemon.js +1 -0
- package/scripts/docs/file-transfer.md +1 -0
- package/scripts/hooks/intent-file-transfer.js +2 -1
- package/scripts/hooks/intent-perpetual.js +109 -0
- package/scripts/hooks/intent-research.js +112 -0
- package/scripts/intent-registry.js +4 -0
- package/scripts/ops-mission-queue.js +258 -0
- package/scripts/ops-verifier.js +197 -0
- package/skills/agent-browser/SKILL.md +153 -0
- package/skills/agent-reach/SKILL.md +66 -0
- package/skills/agent-reach/evolution.json +13 -0
- package/skills/deep-research/SKILL.md +77 -0
- package/skills/find-skills/SKILL.md +133 -0
- package/skills/heartbeat-task-manager/SKILL.md +63 -0
- package/skills/macos-local-orchestrator/SKILL.md +192 -0
- package/skills/macos-local-orchestrator/agents/openai.yaml +4 -0
- package/skills/macos-local-orchestrator/references/tooling-landscape.md +70 -0
- package/skills/macos-mail-calendar/SKILL.md +394 -0
- package/skills/mcp-installer/SKILL.md +138 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/README.md +72 -0
- package/skills/skill-creator/SKILL.md +96 -0
- package/skills/skill-creator/evolution.json +6 -0
- package/skills/skill-creator/references/creation-guide.md +116 -0
- package/skills/skill-creator/references/evolution-guide.md +74 -0
- package/skills/skill-creator/references/output-patterns.md +82 -0
- package/skills/skill-creator/references/workflows.md +28 -0
- package/skills/skill-creator/scripts/align_all.py +32 -0
- package/skills/skill-creator/scripts/auto_evolve_hook.js +247 -0
- package/skills/skill-creator/scripts/init_skill.py +303 -0
- package/skills/skill-creator/scripts/merge_evolution.py +70 -0
- package/skills/skill-creator/scripts/package_skill.py +110 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/setup.py +141 -0
- package/skills/skill-creator/scripts/smart_stitch.py +82 -0
- package/skills/skill-manager/SKILL.md +112 -0
- package/skills/skill-manager/scripts/delete_skill.py +31 -0
- package/skills/skill-manager/scripts/list_skills.py +61 -0
- package/skills/skill-manager/scripts/scan_and_check.py +125 -0
- package/skills/skill-manager/scripts/sync_index.py +144 -0
- package/skills/skill-manager/scripts/update_helper.py +39 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Skill Packager - Creates a distributable .skill file of a skill folder
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python utils/package_skill.py <path/to/skill-folder> [output-directory]
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
python utils/package_skill.py skills/public/my-skill
|
|
10
|
+
python utils/package_skill.py skills/public/my-skill ./dist
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
import zipfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from quick_validate import validate_skill
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def package_skill(skill_path, output_dir=None):
|
|
20
|
+
"""
|
|
21
|
+
Package a skill folder into a .skill file.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
skill_path: Path to the skill folder
|
|
25
|
+
output_dir: Optional output directory for the .skill file (defaults to current directory)
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Path to the created .skill file, or None if error
|
|
29
|
+
"""
|
|
30
|
+
skill_path = Path(skill_path).resolve()
|
|
31
|
+
|
|
32
|
+
# Validate skill folder exists
|
|
33
|
+
if not skill_path.exists():
|
|
34
|
+
print(f"❌ Error: Skill folder not found: {skill_path}")
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
if not skill_path.is_dir():
|
|
38
|
+
print(f"❌ Error: Path is not a directory: {skill_path}")
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
# Validate SKILL.md exists
|
|
42
|
+
skill_md = skill_path / "SKILL.md"
|
|
43
|
+
if not skill_md.exists():
|
|
44
|
+
print(f"❌ Error: SKILL.md not found in {skill_path}")
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
# Run validation before packaging
|
|
48
|
+
print("🔍 Validating skill...")
|
|
49
|
+
valid, message = validate_skill(skill_path)
|
|
50
|
+
if not valid:
|
|
51
|
+
print(f"❌ Validation failed: {message}")
|
|
52
|
+
print(" Please fix the validation errors before packaging.")
|
|
53
|
+
return None
|
|
54
|
+
print(f"✅ {message}\n")
|
|
55
|
+
|
|
56
|
+
# Determine output location
|
|
57
|
+
skill_name = skill_path.name
|
|
58
|
+
if output_dir:
|
|
59
|
+
output_path = Path(output_dir).resolve()
|
|
60
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
else:
|
|
62
|
+
output_path = Path.cwd()
|
|
63
|
+
|
|
64
|
+
skill_filename = output_path / f"{skill_name}.skill"
|
|
65
|
+
|
|
66
|
+
# Create the .skill file (zip format)
|
|
67
|
+
try:
|
|
68
|
+
with zipfile.ZipFile(skill_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
69
|
+
# Walk through the skill directory
|
|
70
|
+
for file_path in skill_path.rglob('*'):
|
|
71
|
+
if file_path.is_file():
|
|
72
|
+
# Calculate the relative path within the zip
|
|
73
|
+
arcname = file_path.relative_to(skill_path.parent)
|
|
74
|
+
zipf.write(file_path, arcname)
|
|
75
|
+
print(f" Added: {arcname}")
|
|
76
|
+
|
|
77
|
+
print(f"\n✅ Successfully packaged skill to: {skill_filename}")
|
|
78
|
+
return skill_filename
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f"❌ Error creating .skill file: {e}")
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def main():
|
|
86
|
+
if len(sys.argv) < 2:
|
|
87
|
+
print("Usage: python utils/package_skill.py <path/to/skill-folder> [output-directory]")
|
|
88
|
+
print("\nExample:")
|
|
89
|
+
print(" python utils/package_skill.py skills/public/my-skill")
|
|
90
|
+
print(" python utils/package_skill.py skills/public/my-skill ./dist")
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
skill_path = sys.argv[1]
|
|
94
|
+
output_dir = sys.argv[2] if len(sys.argv) > 2 else None
|
|
95
|
+
|
|
96
|
+
print(f"📦 Packaging skill: {skill_path}")
|
|
97
|
+
if output_dir:
|
|
98
|
+
print(f" Output directory: {output_dir}")
|
|
99
|
+
print()
|
|
100
|
+
|
|
101
|
+
result = package_skill(skill_path, output_dir)
|
|
102
|
+
|
|
103
|
+
if result:
|
|
104
|
+
sys.exit(0)
|
|
105
|
+
else:
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
main()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Quick validation script for skills - minimal version
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import yaml
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
def validate_skill(skill_path):
|
|
13
|
+
"""Basic validation of a skill"""
|
|
14
|
+
skill_path = Path(skill_path)
|
|
15
|
+
|
|
16
|
+
# Check SKILL.md exists
|
|
17
|
+
skill_md = skill_path / 'SKILL.md'
|
|
18
|
+
if not skill_md.exists():
|
|
19
|
+
return False, "SKILL.md not found"
|
|
20
|
+
|
|
21
|
+
# Read and validate frontmatter
|
|
22
|
+
content = skill_md.read_text()
|
|
23
|
+
if not content.startswith('---'):
|
|
24
|
+
return False, "No YAML frontmatter found"
|
|
25
|
+
|
|
26
|
+
# Extract frontmatter
|
|
27
|
+
match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
|
|
28
|
+
if not match:
|
|
29
|
+
return False, "Invalid frontmatter format"
|
|
30
|
+
|
|
31
|
+
frontmatter_text = match.group(1)
|
|
32
|
+
|
|
33
|
+
# Parse YAML frontmatter
|
|
34
|
+
try:
|
|
35
|
+
frontmatter = yaml.safe_load(frontmatter_text)
|
|
36
|
+
if not isinstance(frontmatter, dict):
|
|
37
|
+
return False, "Frontmatter must be a YAML dictionary"
|
|
38
|
+
except yaml.YAMLError as e:
|
|
39
|
+
return False, f"Invalid YAML in frontmatter: {e}"
|
|
40
|
+
|
|
41
|
+
# Define allowed properties
|
|
42
|
+
ALLOWED_PROPERTIES = {'name', 'description', 'license', 'allowed-tools', 'metadata', 'compatibility'}
|
|
43
|
+
|
|
44
|
+
# Check for unexpected properties (excluding nested keys under metadata)
|
|
45
|
+
unexpected_keys = set(frontmatter.keys()) - ALLOWED_PROPERTIES
|
|
46
|
+
if unexpected_keys:
|
|
47
|
+
return False, (
|
|
48
|
+
f"Unexpected key(s) in SKILL.md frontmatter: {', '.join(sorted(unexpected_keys))}. "
|
|
49
|
+
f"Allowed properties are: {', '.join(sorted(ALLOWED_PROPERTIES))}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Check required fields
|
|
53
|
+
if 'name' not in frontmatter:
|
|
54
|
+
return False, "Missing 'name' in frontmatter"
|
|
55
|
+
if 'description' not in frontmatter:
|
|
56
|
+
return False, "Missing 'description' in frontmatter"
|
|
57
|
+
|
|
58
|
+
# Extract name for validation
|
|
59
|
+
name = frontmatter.get('name', '')
|
|
60
|
+
if not isinstance(name, str):
|
|
61
|
+
return False, f"Name must be a string, got {type(name).__name__}"
|
|
62
|
+
name = name.strip()
|
|
63
|
+
if name:
|
|
64
|
+
# Check naming convention (kebab-case: lowercase with hyphens)
|
|
65
|
+
if not re.match(r'^[a-z0-9-]+$', name):
|
|
66
|
+
return False, f"Name '{name}' should be kebab-case (lowercase letters, digits, and hyphens only)"
|
|
67
|
+
if name.startswith('-') or name.endswith('-') or '--' in name:
|
|
68
|
+
return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens"
|
|
69
|
+
# Check name length (max 64 characters per spec)
|
|
70
|
+
if len(name) > 64:
|
|
71
|
+
return False, f"Name is too long ({len(name)} characters). Maximum is 64 characters."
|
|
72
|
+
|
|
73
|
+
# Extract and validate description
|
|
74
|
+
description = frontmatter.get('description', '')
|
|
75
|
+
if not isinstance(description, str):
|
|
76
|
+
return False, f"Description must be a string, got {type(description).__name__}"
|
|
77
|
+
description = description.strip()
|
|
78
|
+
if description:
|
|
79
|
+
# Check for angle brackets
|
|
80
|
+
if '<' in description or '>' in description:
|
|
81
|
+
return False, "Description cannot contain angle brackets (< or >)"
|
|
82
|
+
# Check description length (max 1024 characters per spec)
|
|
83
|
+
if len(description) > 1024:
|
|
84
|
+
return False, f"Description is too long ({len(description)} characters). Maximum is 1024 characters."
|
|
85
|
+
|
|
86
|
+
# Validate compatibility field if present (optional)
|
|
87
|
+
compatibility = frontmatter.get('compatibility', '')
|
|
88
|
+
if compatibility:
|
|
89
|
+
if not isinstance(compatibility, str):
|
|
90
|
+
return False, f"Compatibility must be a string, got {type(compatibility).__name__}"
|
|
91
|
+
if len(compatibility) > 500:
|
|
92
|
+
return False, f"Compatibility is too long ({len(compatibility)} characters). Maximum is 500 characters."
|
|
93
|
+
|
|
94
|
+
return True, "Skill is valid!"
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
if len(sys.argv) != 2:
|
|
98
|
+
print("Usage: python quick_validate.py <skill_directory>")
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
valid, message = validate_skill(sys.argv[1])
|
|
102
|
+
print(message)
|
|
103
|
+
sys.exit(0 if valid else 1)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
skill-creator setup — auto-configures auto-evolution hook.
|
|
4
|
+
|
|
5
|
+
Detects platform (Claude Code or OpenAI Codex CLI) and installs accordingly.
|
|
6
|
+
Idempotent: safe to run multiple times.
|
|
7
|
+
|
|
8
|
+
python3 ~/.claude/skills/skill-creator/scripts/setup.py # CC
|
|
9
|
+
python3 ~/.codex/skills/skill-creator/scripts/setup.py # Codex
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
CLAUDE_DIR = os.path.expanduser('~/.claude')
|
|
17
|
+
CODEX_DIR = os.path.expanduser('~/.codex')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_hook_path():
|
|
21
|
+
scripts_dir = os.path.dirname(os.path.abspath(__file__))
|
|
22
|
+
return os.path.join(scripts_dir, 'auto_evolve_hook.js')
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def detect_platform():
|
|
26
|
+
"""Detect whether running under Claude Code or OpenAI Codex CLI."""
|
|
27
|
+
# Prefer whichever directory contains the installed skill-creator
|
|
28
|
+
hook = get_hook_path()
|
|
29
|
+
if CLAUDE_DIR in hook or os.path.exists(os.path.join(CLAUDE_DIR, 'settings.json')):
|
|
30
|
+
return 'cc'
|
|
31
|
+
if CODEX_DIR in hook or os.path.exists(os.path.join(CODEX_DIR, 'config.toml')):
|
|
32
|
+
return 'codex'
|
|
33
|
+
# Fallback: check which dirs exist
|
|
34
|
+
if os.path.exists(CLAUDE_DIR):
|
|
35
|
+
return 'cc'
|
|
36
|
+
if os.path.exists(CODEX_DIR):
|
|
37
|
+
return 'codex'
|
|
38
|
+
return 'cc' # default
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ── Claude Code ───────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
def cc_is_installed(hook_path):
|
|
44
|
+
settings_file = os.path.join(CLAUDE_DIR, 'settings.json')
|
|
45
|
+
if not os.path.exists(settings_file):
|
|
46
|
+
return False
|
|
47
|
+
try:
|
|
48
|
+
with open(settings_file, 'r', encoding='utf-8') as f:
|
|
49
|
+
s = json.load(f)
|
|
50
|
+
for group in s.get('hooks', {}).get('Stop', []):
|
|
51
|
+
for h in group.get('hooks', []):
|
|
52
|
+
if hook_path in h.get('command', ''):
|
|
53
|
+
return True
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def cc_install(hook_path):
|
|
60
|
+
settings_file = os.path.join(CLAUDE_DIR, 'settings.json')
|
|
61
|
+
os.makedirs(CLAUDE_DIR, exist_ok=True)
|
|
62
|
+
settings = {}
|
|
63
|
+
if os.path.exists(settings_file):
|
|
64
|
+
try:
|
|
65
|
+
with open(settings_file, 'r', encoding='utf-8') as f:
|
|
66
|
+
settings = json.load(f)
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
hooks = settings.setdefault('hooks', {})
|
|
70
|
+
hooks.setdefault('Stop', []).append({
|
|
71
|
+
'hooks': [{'type': 'command', 'command': f'node "{hook_path}"'}]
|
|
72
|
+
})
|
|
73
|
+
with open(settings_file, 'w', encoding='utf-8') as f:
|
|
74
|
+
json.dump(settings, f, indent=2, ensure_ascii=False)
|
|
75
|
+
f.write('\n')
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ── OpenAI Codex CLI ──────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
def codex_is_installed(hook_path):
|
|
81
|
+
config_file = os.path.join(CODEX_DIR, 'config.toml')
|
|
82
|
+
if not os.path.exists(config_file):
|
|
83
|
+
return False
|
|
84
|
+
with open(config_file, 'r', encoding='utf-8') as f:
|
|
85
|
+
return 'auto_evolve_hook' in f.read()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def codex_install(hook_path):
|
|
89
|
+
"""
|
|
90
|
+
Codex uses config.toml + notify hook (fires on agent-turn-complete).
|
|
91
|
+
Note: Codex notify does NOT pass transcript data — evolution runs in
|
|
92
|
+
signal-only mode (tool failures captured per-turn, no Haiku analysis).
|
|
93
|
+
"""
|
|
94
|
+
config_file = os.path.join(CODEX_DIR, 'config.toml')
|
|
95
|
+
os.makedirs(CODEX_DIR, exist_ok=True)
|
|
96
|
+
entry = f'\n# skill-creator: auto-evolution hook (fires on agent-turn-complete)\nnotify = ["node", "{hook_path}", "--codex"]\n'
|
|
97
|
+
with open(config_file, 'a', encoding='utf-8') as f:
|
|
98
|
+
f.write(entry)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ── Main ──────────────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
def main():
|
|
104
|
+
hook_path = get_hook_path()
|
|
105
|
+
|
|
106
|
+
if not os.path.exists(hook_path):
|
|
107
|
+
print(f'Error: hook script not found at:\n {hook_path}', file=sys.stderr)
|
|
108
|
+
print('Make sure skill-creator is fully installed.', file=sys.stderr)
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
platform = detect_platform()
|
|
112
|
+
|
|
113
|
+
if platform == 'cc':
|
|
114
|
+
if cc_is_installed(hook_path):
|
|
115
|
+
print('✓ Auto-evolve hook already configured (Claude Code). Nothing to do.')
|
|
116
|
+
return
|
|
117
|
+
cc_install(hook_path)
|
|
118
|
+
has_key = bool(os.environ.get('ANTHROPIC_API_KEY'))
|
|
119
|
+
print('✓ Auto-evolve hook installed (Claude Code).')
|
|
120
|
+
print(f' Config: ~/.claude/settings.json → Stop hook')
|
|
121
|
+
if has_key:
|
|
122
|
+
print(' Mode: Haiku-powered analysis (ANTHROPIC_API_KEY detected)')
|
|
123
|
+
else:
|
|
124
|
+
print(' Mode: Rule-based signal capture')
|
|
125
|
+
print(' Tip: Set ANTHROPIC_API_KEY to enable Haiku-powered experience analysis.')
|
|
126
|
+
print(' Skills evolve automatically at the end of each session.')
|
|
127
|
+
|
|
128
|
+
else: # codex
|
|
129
|
+
if codex_is_installed(hook_path):
|
|
130
|
+
print('✓ Auto-evolve hook already configured (Codex CLI). Nothing to do.')
|
|
131
|
+
return
|
|
132
|
+
codex_install(hook_path)
|
|
133
|
+
print('✓ Auto-evolve hook installed (Codex CLI).')
|
|
134
|
+
print(f' Config: ~/.codex/config.toml → notify hook')
|
|
135
|
+
print(' Mode: Signal capture per turn (Codex notify has no transcript access)')
|
|
136
|
+
print(' Note: Full Haiku analysis not available on Codex — tool failures are')
|
|
137
|
+
print(' recorded each turn and stitched into SKILL.md incrementally.')
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == '__main__':
|
|
141
|
+
main()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
def stitch_skill(skill_dir):
|
|
7
|
+
"""
|
|
8
|
+
Reads evolution.json and stitches it into SKILL.md under a dedicated section.
|
|
9
|
+
"""
|
|
10
|
+
skill_md_path = os.path.join(skill_dir, "SKILL.md")
|
|
11
|
+
evolution_json_path = os.path.join(skill_dir, "evolution.json")
|
|
12
|
+
|
|
13
|
+
if not os.path.exists(skill_md_path):
|
|
14
|
+
print(f"Error: SKILL.md not found in {skill_dir}", file=sys.stderr)
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
if not os.path.exists(evolution_json_path):
|
|
18
|
+
print(f"Info: No evolution.json found in {skill_dir}. Nothing to stitch.", file=sys.stderr)
|
|
19
|
+
return True
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
with open(evolution_json_path, 'r', encoding='utf-8') as f:
|
|
23
|
+
data = json.load(f)
|
|
24
|
+
except Exception as e:
|
|
25
|
+
print(f"Error parsing evolution.json: {e}", file=sys.stderr)
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
# Prepare the Markdown content block
|
|
29
|
+
evolution_section = []
|
|
30
|
+
evolution_section.append("\n\n## User-Learned Best Practices & Constraints")
|
|
31
|
+
evolution_section.append("\n> **Auto-Generated Section**: This section is maintained by `skill-creator`. Do not edit manually.")
|
|
32
|
+
|
|
33
|
+
if data.get("preferences"):
|
|
34
|
+
evolution_section.append("\n### User Preferences")
|
|
35
|
+
for item in data["preferences"]:
|
|
36
|
+
evolution_section.append(f"- {item}")
|
|
37
|
+
|
|
38
|
+
if data.get("fixes"):
|
|
39
|
+
evolution_section.append("\n### Known Fixes & Workarounds")
|
|
40
|
+
for item in data["fixes"]:
|
|
41
|
+
evolution_section.append(f"- {item}")
|
|
42
|
+
|
|
43
|
+
if data.get("custom_prompts"):
|
|
44
|
+
evolution_section.append("\n### Custom Instruction Injection")
|
|
45
|
+
evolution_section.append(f"\n{data['custom_prompts']}")
|
|
46
|
+
|
|
47
|
+
evolution_block = "\n".join(evolution_section)
|
|
48
|
+
|
|
49
|
+
# Read original SKILL.md
|
|
50
|
+
with open(skill_md_path, 'r', encoding='utf-8') as f:
|
|
51
|
+
content = f.read()
|
|
52
|
+
|
|
53
|
+
# Regex to find existing User-Learned section and replace it, or append if not found
|
|
54
|
+
# Pattern looks for "## User-Learned Best Practices..." until end of file
|
|
55
|
+
pattern = r"(\n+## User-Learned Best Practices & Constraints.*$)"
|
|
56
|
+
|
|
57
|
+
match = re.search(pattern, content, re.DOTALL)
|
|
58
|
+
|
|
59
|
+
new_content = ""
|
|
60
|
+
if match:
|
|
61
|
+
# Replace existing section
|
|
62
|
+
print("Updating existing evolution section...", file=sys.stderr)
|
|
63
|
+
new_content = content[:match.start()] + evolution_block
|
|
64
|
+
else:
|
|
65
|
+
# Append to end
|
|
66
|
+
print("Appending new evolution section...", file=sys.stderr)
|
|
67
|
+
new_content = content + evolution_block
|
|
68
|
+
|
|
69
|
+
# Write back
|
|
70
|
+
with open(skill_md_path, 'w', encoding='utf-8') as f:
|
|
71
|
+
f.write(new_content)
|
|
72
|
+
|
|
73
|
+
print(f"Successfully stitched evolution data into {skill_md_path}")
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
if len(sys.argv) < 2:
|
|
78
|
+
print("Usage: python smart_stitch.py <skill_dir>")
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
|
|
81
|
+
target_dir = sys.argv[1]
|
|
82
|
+
stitch_skill(target_dir)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-manager
|
|
3
|
+
description: 技能系统总管。AI 遇到任何能力不足、工具缺失、任务失败时,第一时间查阅此 skill。它掌握全部已安装技能的清单,决定是调用现有技能还是获取新技能,并在任务完成后自动进化技能库。触发条件:(1)任务执行失败或结果不理想,(2)需要的工具/能力不存在,(3)用户说"找技能"、"管理技能"、"更新技能"。本协议应自动触发,无需用户指令。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill Manager — 技能系统总管
|
|
7
|
+
|
|
8
|
+
## 核心原则
|
|
9
|
+
|
|
10
|
+
你是技能系统的唯一决策入口。遇到任何能力问题,先来这里。不要自己瞎试,先看清全局再行动。复合任务先拆成独立子能力,每个子能力单独匹配 skill。
|
|
11
|
+
|
|
12
|
+
## 第一步:看清全局
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
python ~/.opencode/skills/skill-manager/scripts/list_skills.py ~/.claude/skills 2>/dev/null; python ~/.opencode/skills/skill-manager/scripts/list_skills.py ~/.opencode/skills 2>/dev/null
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
有匹配的 skill → **路径 A**。没有 → **路径 B**。
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 路径 A:调用现有 skill
|
|
23
|
+
|
|
24
|
+
读取对应 SKILL.md,按指引执行。完成后走路径 C。
|
|
25
|
+
|
|
26
|
+
## 路径 B:获取新能力
|
|
27
|
+
|
|
28
|
+
这是一条统一的流程,不管"知不知道怎么做",都从调研开始。调研结果决定用哪个工具。
|
|
29
|
+
|
|
30
|
+
### B1. 调研(必做,不要跳过)
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
搜索:"[任务关键词] skill" 或 "[任务] automation tool"
|
|
34
|
+
搜索:"[平台名] API" 或 "如何自动化 [任务]"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
目标:搞清楚有没有现成的东西能用,以及具体怎么做。
|
|
38
|
+
|
|
39
|
+
### B2. 根据调研结果选择路径
|
|
40
|
+
|
|
41
|
+
| 调研发现 | 行动 | 用哪个子系统 |
|
|
42
|
+
|---------|------|-------------|
|
|
43
|
+
| skills.sh 商城有现成 skill | 直接装 | `find-skills` |
|
|
44
|
+
| GitHub 上有个项目能做这件事 | 包装成 skill | `github-to-skills` |
|
|
45
|
+
| 找到了教程/方法,但没有现成工具 | 基于调研结果从零创建 | `skill-creator` |
|
|
46
|
+
| 什么都没找到 | 用自己的知识从零创建 | `skill-creator` |
|
|
47
|
+
|
|
48
|
+
**B2a. 商城安装(最便宜)**
|
|
49
|
+
```bash
|
|
50
|
+
npx skills find <关键词>
|
|
51
|
+
npx skills add <owner/repo> -g -y
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**B2b. GitHub 包装(中等成本)**
|
|
55
|
+
|
|
56
|
+
调用 `github-to-skills` skill,提供 GitHub URL,自动生成 skill。
|
|
57
|
+
|
|
58
|
+
**B2c. 从零创建(最贵)**
|
|
59
|
+
|
|
60
|
+
调用 `skill-creator` skill,将调研到的流程写成 SKILL.md:
|
|
61
|
+
- 具体步骤(URL、按钮、等待元素)
|
|
62
|
+
- 前置依赖(Playwright MCP?登录?API Key?)
|
|
63
|
+
- 已知限制和坑
|
|
64
|
+
|
|
65
|
+
### B3. 验证
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
ls ~/.claude/skills/<技能名>/SKILL.md 2>/dev/null || ls ~/.agents/skills/<技能名>/SKILL.md 2>/dev/null
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### B4. 用新 skill 执行原任务
|
|
72
|
+
|
|
73
|
+
不要停下来汇报安装情况,直接继续干活。完成后走路径 C。
|
|
74
|
+
|
|
75
|
+
## 路径 C:进化(任务完成后自动执行)
|
|
76
|
+
|
|
77
|
+
查阅 `skill-evolution-manager`,将本次经验写回 skill。只记有价值的:踩过的坑、用户偏好、优化策略。没有新经验则跳过。
|
|
78
|
+
|
|
79
|
+
## 路径 D:更新过时 skill
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python ~/.opencode/skills/skill-manager/scripts/scan_and_check.py ~/.claude/skills
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
1. `python scripts/update_helper.py <skill_path>` 备份
|
|
86
|
+
2. 拉取新版本
|
|
87
|
+
3. `python ~/.opencode/skills/skill-evolution-manager/scripts/smart_stitch.py <skill_path>` 恢复经验
|
|
88
|
+
|
|
89
|
+
## 子系统索引
|
|
90
|
+
|
|
91
|
+
| 子系统 | Skill 名 | 何时调用 |
|
|
92
|
+
|--------|----------|---------|
|
|
93
|
+
| 商城搜索 | `find-skills` | B2a |
|
|
94
|
+
| GitHub 包装 | `github-to-skills` | B2b |
|
|
95
|
+
| 从零创建 | `skill-creator` | B2c |
|
|
96
|
+
| 经验进化 | `skill-evolution-manager` | 路径 C |
|
|
97
|
+
| 环境修复 | `mcp-installer` | 工具缺失错误 |
|
|
98
|
+
| 深度调研 | `deep-research` | B1(复杂主题时) |
|
|
99
|
+
| 自愈诊断 | `self-diagnose` | Daemon 执行失败自动触发;手机 `/doctor` 手动触发 |
|
|
100
|
+
|
|
101
|
+
## Frontmatter 字段规范
|
|
102
|
+
|
|
103
|
+
扫描/升级 skill 时,确保 frontmatter 包含:
|
|
104
|
+
- `needs_browser: true` — 需要 Playwright 浏览器自动化的 skill 必须声明此字段,否则手机端会跳过 Playwright 加载(省 ~20s)
|
|
105
|
+
|
|
106
|
+
## 约束
|
|
107
|
+
|
|
108
|
+
- 单次最多安装 2 个新技能
|
|
109
|
+
- 优先可信来源(anthropics/、vercel-labs/)
|
|
110
|
+
- 依赖 MCP 的技能先走 `mcp-installer` 自愈协议
|
|
111
|
+
- 商城:https://skills.sh/
|
|
112
|
+
- 删除:`python scripts/delete_skill.py <name> ~/.claude/skills`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import shutil
|
|
4
|
+
|
|
5
|
+
def delete_skill(skills_root, skill_name):
|
|
6
|
+
skill_dir = os.path.join(skills_root, skill_name)
|
|
7
|
+
|
|
8
|
+
if not os.path.exists(skill_dir):
|
|
9
|
+
print(f"Error: Skill '{skill_name}' not found at {skill_dir}")
|
|
10
|
+
return False
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
# Physical deletion
|
|
14
|
+
shutil.rmtree(skill_dir)
|
|
15
|
+
print(f"Successfully deleted skill: {skill_name}")
|
|
16
|
+
return True
|
|
17
|
+
except Exception as e:
|
|
18
|
+
print(f"Error deleting skill '{skill_name}': {e}")
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
if len(sys.argv) < 2:
|
|
23
|
+
print("Usage: python delete_skill.py <skill_name> [skills_root]")
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
|
|
26
|
+
name = sys.argv[1]
|
|
27
|
+
root = r"C:\Users\20515\.claude\skills"
|
|
28
|
+
if len(sys.argv) > 2:
|
|
29
|
+
root = sys.argv[2]
|
|
30
|
+
|
|
31
|
+
delete_skill(root, name)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import yaml
|
|
4
|
+
import io
|
|
5
|
+
|
|
6
|
+
# Force UTF-8 encoding for stdout to handle Chinese characters on Windows
|
|
7
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
8
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
9
|
+
else:
|
|
10
|
+
# Fallback for older Python versions
|
|
11
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
12
|
+
|
|
13
|
+
def list_skills(skills_root):
|
|
14
|
+
if not os.path.exists(skills_root):
|
|
15
|
+
print(f"Error: {skills_root} not found")
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
# Header with Description column
|
|
19
|
+
header = f"{'Skill Name':<20} | {'Type':<12} | {'Description':<40} | {'Ver':<8}"
|
|
20
|
+
print(header)
|
|
21
|
+
print("-" * len(header))
|
|
22
|
+
|
|
23
|
+
for item in os.listdir(skills_root):
|
|
24
|
+
skill_dir = os.path.join(skills_root, item)
|
|
25
|
+
if not os.path.isdir(skill_dir):
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
skill_md = os.path.join(skill_dir, "SKILL.md")
|
|
29
|
+
skill_type = "Standard"
|
|
30
|
+
version = "0.1.0"
|
|
31
|
+
description = "No description"
|
|
32
|
+
|
|
33
|
+
if os.path.exists(skill_md):
|
|
34
|
+
try:
|
|
35
|
+
with open(skill_md, "r", encoding="utf-8") as f:
|
|
36
|
+
content = f.read()
|
|
37
|
+
parts = content.split("---")
|
|
38
|
+
if len(parts) >= 3:
|
|
39
|
+
meta = yaml.safe_load(parts[1])
|
|
40
|
+
if "github_url" in meta:
|
|
41
|
+
skill_type = "GitHub"
|
|
42
|
+
version = str(meta.get("version", "0.1.0"))
|
|
43
|
+
description = meta.get("description", "No description").replace('\n', ' ')
|
|
44
|
+
except:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
# Simple truncation for display
|
|
48
|
+
if len(description) > 37:
|
|
49
|
+
display_desc = description[:37] + "..."
|
|
50
|
+
else:
|
|
51
|
+
display_desc = description
|
|
52
|
+
|
|
53
|
+
# Using a fixed width but acknowledging that Chinese chars take 2 cells
|
|
54
|
+
# This is a basic fix, for perfect alignment one would need wcwidth
|
|
55
|
+
print(f"{item:<20} | {skill_type:<12} | {display_desc:<40} | {version:<8}")
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
skills_path = r"C:\Users\20515\.claude\skills"
|
|
59
|
+
if len(sys.argv) > 1:
|
|
60
|
+
skills_path = sys.argv[1]
|
|
61
|
+
list_skills(skills_path)
|