metame-cli 1.5.11 → 1.5.13
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 +81 -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,125 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import yaml
|
|
4
|
+
import json
|
|
5
|
+
import subprocess
|
|
6
|
+
import concurrent.futures
|
|
7
|
+
|
|
8
|
+
def get_remote_hash(url):
|
|
9
|
+
"""Fetch the latest commit hash from the remote repository."""
|
|
10
|
+
try:
|
|
11
|
+
# Using git ls-remote to avoid downloading the whole repo
|
|
12
|
+
# Asking for HEAD specifically
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
['git', 'ls-remote', url, 'HEAD'],
|
|
15
|
+
capture_output=True,
|
|
16
|
+
text=True,
|
|
17
|
+
timeout=10
|
|
18
|
+
)
|
|
19
|
+
if result.returncode != 0:
|
|
20
|
+
return None
|
|
21
|
+
# Output format: <hash>\tHEAD
|
|
22
|
+
parts = result.stdout.split()
|
|
23
|
+
if parts:
|
|
24
|
+
return parts[0]
|
|
25
|
+
return None
|
|
26
|
+
except Exception:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
def scan_skills(skills_root):
|
|
30
|
+
"""Scan all subdirectories for SKILL.md and extract metadata."""
|
|
31
|
+
skill_list = []
|
|
32
|
+
|
|
33
|
+
if not os.path.exists(skills_root):
|
|
34
|
+
print(f"Skills root not found: {skills_root}", file=sys.stderr)
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
for item in os.listdir(skills_root):
|
|
38
|
+
skill_dir = os.path.join(skills_root, item)
|
|
39
|
+
if not os.path.isdir(skill_dir):
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
skill_md = os.path.join(skill_dir, "SKILL.md")
|
|
43
|
+
if not os.path.exists(skill_md):
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
# Parse Frontmatter
|
|
47
|
+
try:
|
|
48
|
+
with open(skill_md, 'r', encoding='utf-8') as f:
|
|
49
|
+
content = f.read()
|
|
50
|
+
|
|
51
|
+
# Extract YAML between first two ---
|
|
52
|
+
parts = content.split('---')
|
|
53
|
+
if len(parts) < 3:
|
|
54
|
+
continue # Invalid format
|
|
55
|
+
|
|
56
|
+
frontmatter = yaml.safe_load(parts[1])
|
|
57
|
+
|
|
58
|
+
# Check if managed by github-to-skills
|
|
59
|
+
if 'github_url' in frontmatter:
|
|
60
|
+
skill_list.append({
|
|
61
|
+
"name": frontmatter.get('name', item),
|
|
62
|
+
"dir": skill_dir,
|
|
63
|
+
"github_url": frontmatter['github_url'],
|
|
64
|
+
"local_hash": frontmatter.get('github_hash', 'unknown'),
|
|
65
|
+
"local_version": frontmatter.get('version', '0.0.0')
|
|
66
|
+
})
|
|
67
|
+
except Exception as e:
|
|
68
|
+
# print(f"Skipping {item}: {e}", file=sys.stderr)
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
return skill_list
|
|
72
|
+
|
|
73
|
+
def check_updates(skills):
|
|
74
|
+
"""Check for updates concurrently."""
|
|
75
|
+
results = []
|
|
76
|
+
|
|
77
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
|
|
78
|
+
# Create a map of future -> skill
|
|
79
|
+
future_to_skill = {
|
|
80
|
+
executor.submit(get_remote_hash, skill['github_url']): skill
|
|
81
|
+
for skill in skills
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for future in concurrent.futures.as_completed(future_to_skill):
|
|
85
|
+
skill = future_to_skill[future]
|
|
86
|
+
try:
|
|
87
|
+
remote_hash = future.result()
|
|
88
|
+
skill['remote_hash'] = remote_hash
|
|
89
|
+
|
|
90
|
+
if not remote_hash:
|
|
91
|
+
skill['status'] = 'error'
|
|
92
|
+
skill['message'] = 'Could not reach remote'
|
|
93
|
+
elif remote_hash != skill['local_hash']:
|
|
94
|
+
skill['status'] = 'outdated'
|
|
95
|
+
skill['message'] = 'New commits available'
|
|
96
|
+
else:
|
|
97
|
+
skill['status'] = 'current'
|
|
98
|
+
skill['message'] = 'Up to date'
|
|
99
|
+
|
|
100
|
+
results.append(skill)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
skill['status'] = 'error'
|
|
103
|
+
skill['message'] = str(e)
|
|
104
|
+
results.append(skill)
|
|
105
|
+
|
|
106
|
+
return results
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
if len(sys.argv) < 2:
|
|
110
|
+
# Default to standard Claude skills path if not provided
|
|
111
|
+
# Trying to guess typical Windows path for this user context
|
|
112
|
+
default_path = os.path.expanduser(r"~\.claude\skills")
|
|
113
|
+
# But we are in a tool env, let's use the provided one or current dir
|
|
114
|
+
if os.path.exists(r"C:\Users\20515\.claude\skills"):
|
|
115
|
+
target_dir = r"C:\Users\20515\.claude\skills"
|
|
116
|
+
else:
|
|
117
|
+
print("Usage: python scan_and_check.py <skills_dir>")
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
else:
|
|
120
|
+
target_dir = sys.argv[1]
|
|
121
|
+
|
|
122
|
+
skills = scan_skills(target_dir)
|
|
123
|
+
updates = check_updates(skills)
|
|
124
|
+
|
|
125
|
+
print(json.dumps(updates, indent=2))
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
START_MARKER = "<!-- AUTO-SKILL-INDEX:START -->"
|
|
11
|
+
END_MARKER = "<!-- AUTO-SKILL-INDEX:END -->"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_frontmatter(skill_md_path: Path):
|
|
15
|
+
try:
|
|
16
|
+
text = skill_md_path.read_text(encoding="utf-8")
|
|
17
|
+
except Exception:
|
|
18
|
+
return None, None
|
|
19
|
+
|
|
20
|
+
m = re.match(r"^---\n(.*?)\n---", text, re.DOTALL)
|
|
21
|
+
if not m:
|
|
22
|
+
return None, None
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
meta = yaml.safe_load(m.group(1)) or {}
|
|
26
|
+
except Exception:
|
|
27
|
+
return None, None
|
|
28
|
+
|
|
29
|
+
name = str(meta.get("name") or "").strip()
|
|
30
|
+
desc = str(meta.get("description") or "").strip()
|
|
31
|
+
return name, desc
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def short_desc(text: str, limit: int = 72):
|
|
35
|
+
clean = re.sub(r"\s+", " ", text).strip()
|
|
36
|
+
if len(clean) <= limit:
|
|
37
|
+
return clean
|
|
38
|
+
return clean[: limit - 3] + "..."
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def table_escape(text: str):
|
|
42
|
+
return text.replace("|", "\\|")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def source_label(root: Path):
|
|
46
|
+
s = str(root)
|
|
47
|
+
if "/.claude/" in s:
|
|
48
|
+
return "claude"
|
|
49
|
+
if "/.opencode/" in s:
|
|
50
|
+
return "opencode"
|
|
51
|
+
return root.name or "local"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def collect_skills(roots):
|
|
55
|
+
skills = {}
|
|
56
|
+
for root in roots:
|
|
57
|
+
if not root.exists() or not root.is_dir():
|
|
58
|
+
continue
|
|
59
|
+
src = source_label(root)
|
|
60
|
+
for entry in sorted(root.iterdir(), key=lambda p: p.name.lower()):
|
|
61
|
+
if entry.name.startswith("."):
|
|
62
|
+
continue
|
|
63
|
+
if not entry.is_dir():
|
|
64
|
+
continue
|
|
65
|
+
skill_md = entry / "SKILL.md"
|
|
66
|
+
if not skill_md.exists():
|
|
67
|
+
continue
|
|
68
|
+
name, desc = parse_frontmatter(skill_md)
|
|
69
|
+
if not name:
|
|
70
|
+
name = entry.name
|
|
71
|
+
if name in skills:
|
|
72
|
+
continue
|
|
73
|
+
skills[name] = {
|
|
74
|
+
"source": src,
|
|
75
|
+
"desc": short_desc(desc or "No description"),
|
|
76
|
+
}
|
|
77
|
+
return skills
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def render_table(skills):
|
|
81
|
+
lines = [
|
|
82
|
+
"| Skill 名 | 来源 | 描述摘要 |",
|
|
83
|
+
"|---|---|---|",
|
|
84
|
+
]
|
|
85
|
+
for name in sorted(skills.keys(), key=str.lower):
|
|
86
|
+
item = skills[name]
|
|
87
|
+
lines.append(
|
|
88
|
+
f"| `{table_escape(name)}` | {table_escape(item['source'])} | {table_escape(item['desc'])} |"
|
|
89
|
+
)
|
|
90
|
+
return "\n".join(lines)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def inject_table(skill_md_path: Path, table_md: str):
|
|
94
|
+
text = skill_md_path.read_text(encoding="utf-8")
|
|
95
|
+
block = f"{START_MARKER}\n{table_md}\n{END_MARKER}"
|
|
96
|
+
|
|
97
|
+
if START_MARKER in text and END_MARKER in text:
|
|
98
|
+
pattern = re.compile(
|
|
99
|
+
re.escape(START_MARKER) + r".*?" + re.escape(END_MARKER),
|
|
100
|
+
re.DOTALL,
|
|
101
|
+
)
|
|
102
|
+
new_text = pattern.sub(block, text)
|
|
103
|
+
else:
|
|
104
|
+
append = (
|
|
105
|
+
"\n\n## 已注册技能清单(自动生成)\n\n"
|
|
106
|
+
"由 `scripts/sync_index.py` 维护,请勿手工编辑该区块。\n\n"
|
|
107
|
+
f"{block}\n"
|
|
108
|
+
)
|
|
109
|
+
new_text = text + append
|
|
110
|
+
|
|
111
|
+
skill_md_path.write_text(new_text, encoding="utf-8")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def main():
|
|
115
|
+
parser = argparse.ArgumentParser(
|
|
116
|
+
description="Scan skills folders and sync auto-generated skill index section in SKILL.md."
|
|
117
|
+
)
|
|
118
|
+
parser.add_argument(
|
|
119
|
+
"--skill-md",
|
|
120
|
+
required=True,
|
|
121
|
+
help="Path to skill-manager SKILL.md to update.",
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"roots",
|
|
125
|
+
nargs="+",
|
|
126
|
+
help="Skill root folders to scan (e.g. ~/.claude/skills ~/.opencode/skills).",
|
|
127
|
+
)
|
|
128
|
+
args = parser.parse_args()
|
|
129
|
+
|
|
130
|
+
skill_md_path = Path(os.path.expanduser(args.skill_md)).resolve()
|
|
131
|
+
roots = [Path(os.path.expanduser(r)).resolve() for r in args.roots]
|
|
132
|
+
|
|
133
|
+
if not skill_md_path.exists():
|
|
134
|
+
print(f"[ERROR] skill markdown not found: {skill_md_path}")
|
|
135
|
+
sys.exit(1)
|
|
136
|
+
|
|
137
|
+
skills = collect_skills(roots)
|
|
138
|
+
table_md = render_table(skills)
|
|
139
|
+
inject_table(skill_md_path, table_md)
|
|
140
|
+
print(f"[OK] synced {len(skills)} skills into {skill_md_path}")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import datetime
|
|
5
|
+
|
|
6
|
+
def backup_skill(skill_path):
|
|
7
|
+
"""
|
|
8
|
+
Backs up SKILL.md to SKILL.md.bak.<timestamp>
|
|
9
|
+
"""
|
|
10
|
+
if not os.path.exists(skill_path):
|
|
11
|
+
return False, "Skill path does not exist"
|
|
12
|
+
|
|
13
|
+
skill_md = os.path.join(skill_path, "SKILL.md")
|
|
14
|
+
if not os.path.exists(skill_md):
|
|
15
|
+
return False, "SKILL.md not found"
|
|
16
|
+
|
|
17
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
18
|
+
backup_name = f"SKILL.md.bak.{timestamp}"
|
|
19
|
+
backup_path = os.path.join(skill_path, backup_name)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
shutil.copy2(skill_md, backup_path)
|
|
23
|
+
return True, backup_path
|
|
24
|
+
except Exception as e:
|
|
25
|
+
return False, str(e)
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
if len(sys.argv) < 2:
|
|
29
|
+
print("Usage: python update_helper.py <skill_dir>")
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
skill_dir = sys.argv[1]
|
|
33
|
+
success, msg = backup_skill(skill_dir)
|
|
34
|
+
|
|
35
|
+
if success:
|
|
36
|
+
print(f"Backup created: {msg}")
|
|
37
|
+
else:
|
|
38
|
+
print(f"Backup failed: {msg}")
|
|
39
|
+
sys.exit(1)
|