pythonclaw 0.6.4__tar.gz → 0.6.5__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.
Files changed (131) hide show
  1. {pythonclaw-0.6.4/pythonclaw.egg-info → pythonclaw-0.6.5}/PKG-INFO +1 -1
  2. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pyproject.toml +1 -1
  3. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/skillhub.py +61 -81
  4. {pythonclaw-0.6.4 → pythonclaw-0.6.5/pythonclaw.egg-info}/PKG-INFO +1 -1
  5. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/LICENSE +0 -0
  6. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/MANIFEST.in +0 -0
  7. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/README.md +0 -0
  8. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/__init__.py +0 -0
  9. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/__main__.py +0 -0
  10. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/channels/discord_bot.py +0 -0
  11. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/channels/telegram_bot.py +0 -0
  12. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/channels/whatsapp_bot.py +0 -0
  13. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/config.py +0 -0
  14. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/__init__.py +0 -0
  15. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/agent.py +0 -0
  16. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/compaction.py +0 -0
  17. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/knowledge/rag.py +0 -0
  18. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/llm/anthropic_client.py +0 -0
  19. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/llm/base.py +0 -0
  20. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/llm/gemini_client.py +0 -0
  21. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/llm/openai_compatible.py +0 -0
  22. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/llm/response.py +0 -0
  23. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/memory/manager.py +0 -0
  24. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/memory/storage.py +0 -0
  25. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/persistent_agent.py +0 -0
  26. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/retrieval/__init__.py +0 -0
  27. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/retrieval/chunker.py +0 -0
  28. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/retrieval/dense.py +0 -0
  29. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/retrieval/fusion.py +0 -0
  30. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/retrieval/reranker.py +0 -0
  31. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/retrieval/retriever.py +0 -0
  32. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/retrieval/sparse.py +0 -0
  33. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/session_store.py +0 -0
  34. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/skill_loader.py +0 -0
  35. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/tools.py +0 -0
  36. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/core/utils.py +0 -0
  37. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/daemon.py +0 -0
  38. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/init.py +0 -0
  39. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/main.py +0 -0
  40. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/onboard.py +0 -0
  41. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/scheduler/cron.py +0 -0
  42. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/scheduler/heartbeat.py +0 -0
  43. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/server.py +0 -0
  44. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/session_manager.py +0 -0
  45. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/persona/demo_persona.md +0 -0
  46. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/communication/CATEGORY.md +0 -0
  47. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/communication/email/SKILL.md +0 -0
  48. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/communication/email/send_email.py +0 -0
  49. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/communication/slack/SKILL.md +0 -0
  50. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/communication/slack/slack_api.py +0 -0
  51. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/CATEGORY.md +0 -0
  52. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/csv_analyzer/SKILL.md +0 -0
  53. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/csv_analyzer/analyze.py +0 -0
  54. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/finance/SKILL.md +0 -0
  55. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/finance/fetch_quote.py +0 -0
  56. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/news/SKILL.md +0 -0
  57. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/news/search_news.py +0 -0
  58. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/pdf_reader/SKILL.md +0 -0
  59. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/pdf_reader/read_pdf.py +0 -0
  60. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/scraper/SKILL.md +0 -0
  61. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/scraper/scrape.py +0 -0
  62. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/weather/SKILL.md +0 -0
  63. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/weather/weather.py +0 -0
  64. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/youtube/SKILL.md +0 -0
  65. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/data/youtube/youtube_info.py +0 -0
  66. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/dev/CATEGORY.md +0 -0
  67. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/dev/code_runner/SKILL.md +0 -0
  68. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/dev/code_runner/run_code.py +0 -0
  69. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/dev/github/SKILL.md +0 -0
  70. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/dev/github/gh.py +0 -0
  71. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/dev/http_request/SKILL.md +0 -0
  72. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/dev/http_request/request.py +0 -0
  73. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/google/CATEGORY.md +0 -0
  74. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/google/workspace/SKILL.md +0 -0
  75. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/google/workspace/check_setup.sh +0 -0
  76. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/media/CATEGORY.md +0 -0
  77. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/media/image_gen/SKILL.md +0 -0
  78. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/media/image_gen/generate.py +0 -0
  79. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/media/spotify/SKILL.md +0 -0
  80. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/media/spotify/spotify_ctl.py +0 -0
  81. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/media/tts/SKILL.md +0 -0
  82. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/media/tts/speak.py +0 -0
  83. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/meta/CATEGORY.md +0 -0
  84. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/meta/skill_creator/SKILL.md +0 -0
  85. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/productivity/CATEGORY.md +0 -0
  86. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/productivity/notion/SKILL.md +0 -0
  87. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/productivity/notion/notion_api.py +0 -0
  88. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/productivity/obsidian/SKILL.md +0 -0
  89. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/productivity/obsidian/obsidian_vault.py +0 -0
  90. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/productivity/trello/SKILL.md +0 -0
  91. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/productivity/trello/trello_api.py +0 -0
  92. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/CATEGORY.md +0 -0
  93. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/change_persona/SKILL.md +0 -0
  94. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/change_setting/SKILL.md +0 -0
  95. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/change_setting/update_config.py +0 -0
  96. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/change_soul/SKILL.md +0 -0
  97. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/model_usage/SKILL.md +0 -0
  98. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/model_usage/usage_stats.py +0 -0
  99. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/onboarding/SKILL.md +0 -0
  100. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/onboarding/write_identity.py +0 -0
  101. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/random/SKILL.md +0 -0
  102. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/random/random_util.py +0 -0
  103. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/session_logs/SKILL.md +0 -0
  104. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/session_logs/search_sessions.py +0 -0
  105. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/time/SKILL.md +0 -0
  106. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/system/time/time_util.py +0 -0
  107. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/text/CATEGORY.md +0 -0
  108. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/text/translator/SKILL.md +0 -0
  109. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/text/translator/translate.py +0 -0
  110. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/web/CATEGORY.md +0 -0
  111. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/web/summarize/SKILL.md +0 -0
  112. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/web/summarize/summarize_url.py +0 -0
  113. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/skills/web/tavily/SKILL.md +0 -0
  114. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/soul/SOUL.md +0 -0
  115. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/templates/tools/TOOLS.md +0 -0
  116. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/web/__init__.py +0 -0
  117. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/web/app.py +0 -0
  118. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/web/static/favicon.png +0 -0
  119. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/web/static/index.html +0 -0
  120. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw/web/static/logo.png +0 -0
  121. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw.egg-info/SOURCES.txt +0 -0
  122. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw.egg-info/dependency_links.txt +0 -0
  123. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw.egg-info/entry_points.txt +0 -0
  124. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw.egg-info/requires.txt +0 -0
  125. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/pythonclaw.egg-info/top_level.txt +0 -0
  126. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/setup.cfg +0 -0
  127. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/tests/test_compaction.py +0 -0
  128. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/tests/test_persistence.py +0 -0
  129. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/tests/test_rag_hybrid.py +0 -0
  130. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/tests/test_skills.py +0 -0
  131. {pythonclaw-0.6.4 → pythonclaw-0.6.5}/tests/test_soul.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonclaw
3
- Version: 0.6.4
3
+ Version: 0.6.5
4
4
  Summary: OpenClaw reimagined in pure Python — autonomous AI agent with memory, RAG, skills, web dashboard, and multi-channel support.
5
5
  Author-email: Eric Wang <wangchen2007915@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pythonclaw"
7
- version = "0.6.4"
7
+ version = "0.6.5"
8
8
  description = "OpenClaw reimagined in pure Python — autonomous AI agent with memory, RAG, skills, web dashboard, and multi-channel support."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -15,10 +15,14 @@ Available ClawHub endpoints
15
15
  GET /api/certified — security-verified skills
16
16
  GET /api/stats — platform statistics
17
17
  GET /api/health — API status
18
+
19
+ Skill download (full ZIP with SKILL.md + assets):
20
+ GET https://wry-manatee-359.convex.site/api/v1/download?slug=SLUG
18
21
  """
19
22
 
20
23
  from __future__ import annotations
21
24
 
25
+ import io
22
26
  import json
23
27
  import logging
24
28
  import os
@@ -26,12 +30,14 @@ import re
26
30
  import ssl
27
31
  import urllib.error
28
32
  import urllib.request
33
+ import zipfile
29
34
  from typing import Any
30
35
 
31
36
  logger = logging.getLogger(__name__)
32
37
 
33
38
  CLAWHUB_API = "https://topclawhubskills.com/api"
34
39
  CLAWHUB_WEB = "https://clawhub.com"
40
+ CLAWHUB_DOWNLOAD = "https://wry-manatee-359.convex.site/api/v1/download"
35
41
 
36
42
 
37
43
  def _get_ssl_ctx() -> ssl.SSLContext:
@@ -128,26 +134,16 @@ def browse(
128
134
 
129
135
 
130
136
  def get_skill_detail(skill_id: str) -> dict | None:
131
- """Fetch detail for a skill.
132
-
133
- ClawHub search results already contain summary info. For full
134
- instructions, the skill must be installed (``clawhub install``).
135
- We return whatever metadata we have from the listing.
136
- """
137
+ """Fetch metadata for a skill from ClawHub search API."""
137
138
  try:
138
139
  result = _api_get("/search", params={"q": skill_id})
139
140
  data = result.get("data", [])
140
141
  for s in data:
141
142
  if s.get("slug") == skill_id:
142
- normalized = _normalize([s])[0]
143
- normalized["skill_md"] = _build_skill_md(s)
144
- return normalized
143
+ return _normalize([s])[0]
145
144
 
146
145
  if data:
147
- s = data[0]
148
- normalized = _normalize([s])[0]
149
- normalized["skill_md"] = _build_skill_md(s)
150
- return normalized
146
+ return _normalize([data[0]])[0]
151
147
  except Exception as exc:
152
148
  logger.warning("ClawHub detail fetch failed for '%s': %s", skill_id, exc)
153
149
 
@@ -180,41 +176,19 @@ def verify_api() -> dict:
180
176
 
181
177
  # ── Install ───────────────────────────────────────────────────────────────────
182
178
 
183
- def _build_skill_md(skill: dict) -> str:
184
- """Build a SKILL.md from ClawHub metadata."""
185
- name = skill.get("display_name", skill.get("slug", "unknown"))
186
- slug = skill.get("slug", "")
187
- summary = skill.get("summary", "No description.")
188
- author = skill.get("owner_handle", "")
189
- safe_name = re.sub(r"[^a-zA-Z0-9_]", "_", name.lower()).strip("_")
190
-
191
- lines = [
192
- "---",
193
- f"name: {safe_name}",
194
- "description: >",
195
- f" {summary}",
196
- "---",
197
- "",
198
- f"# {name}",
199
- "",
200
- ]
201
- if author:
202
- lines.append(f"*By @{author} on ClawHub*")
203
- lines.append("")
204
- lines.append(f"Source: {CLAWHUB_WEB}/skills/{slug}")
205
- lines.append("")
206
- lines.append("## Instructions")
207
- lines.append("")
208
- lines.append(f"This skill was imported from ClawHub (`{slug}`).")
209
- lines.append("Refer to the source page for full documentation and usage instructions.")
210
- lines.append("")
211
- if summary:
212
- lines.append("## Description")
213
- lines.append("")
214
- lines.append(summary)
215
- lines.append("")
216
-
217
- return "\n".join(lines)
179
+ def _download_skill_zip(slug: str) -> bytes:
180
+ """Download the full skill ZIP from ClawHub's Convex CDN."""
181
+ url = f"{CLAWHUB_DOWNLOAD}?slug={urllib.request.quote(slug)}"
182
+ req = urllib.request.Request(
183
+ url, headers={"User-Agent": "PythonClaw/1.0"},
184
+ )
185
+ try:
186
+ with urllib.request.urlopen(req, timeout=30, context=_get_ssl_ctx()) as resp:
187
+ return resp.read()
188
+ except Exception as exc:
189
+ raise RuntimeError(
190
+ f"Failed to download skill '{slug}' from ClawHub: {exc}"
191
+ ) from exc
218
192
 
219
193
 
220
194
  def install_skill(
@@ -225,24 +199,16 @@ def install_skill(
225
199
  ) -> str:
226
200
  """Download and install a skill from ClawHub into the local skills directory.
227
201
 
202
+ Downloads the full ZIP archive from ClawHub (contains SKILL.md plus
203
+ any assets, scripts, references, etc.) and extracts it.
204
+
228
205
  Returns the path to the installed skill directory.
229
206
  """
230
207
  if target_dir is None:
231
208
  from .. import config as _cfg
232
209
  target_dir = os.path.join(str(_cfg.PYTHONCLAW_HOME), "context", "skills")
233
210
 
234
- detail = None
235
- if not skill_md_override:
236
- detail = get_skill_detail(skill_id)
237
- if not detail:
238
- raise RuntimeError(f"Could not fetch skill '{skill_id}' from ClawHub.")
239
-
240
- skill_md = skill_md_override or detail.get("skill_md", "")
241
- if not skill_md:
242
- raise RuntimeError(f"No SKILL.md content found for '{skill_id}'.")
243
-
244
- skill_name = _derive_skill_name(skill_id, skill_md, detail)
245
- safe_name = re.sub(r"[^a-zA-Z0-9_-]", "_", skill_name).strip("_")
211
+ safe_name = re.sub(r"[^a-zA-Z0-9_-]", "_", skill_id).strip("_")
246
212
  if not safe_name:
247
213
  safe_name = "imported_skill"
248
214
 
@@ -250,33 +216,47 @@ def install_skill(
250
216
  skill_dir = os.path.join(target_dir, category, safe_name)
251
217
  os.makedirs(skill_dir, exist_ok=True)
252
218
 
253
- md_path = os.path.join(skill_dir, "SKILL.md")
254
- if not skill_md.startswith("---"):
255
- skill_md = f"---\nname: {safe_name}\ndescription: Imported from ClawHub ({skill_id})\n---\n\n{skill_md}"
256
-
257
- with open(md_path, "w", encoding="utf-8") as f:
258
- f.write(skill_md + "\n")
259
-
260
- fallback_url = f"{CLAWHUB_WEB}/skills/{skill_id}"
261
- source_url = detail.get("source_url", fallback_url) if detail else fallback_url
219
+ if skill_md_override:
220
+ md_path = os.path.join(skill_dir, "SKILL.md")
221
+ md = skill_md_override
222
+ if not md.startswith("---"):
223
+ md = f"---\nname: {safe_name}\ndescription: Imported from ClawHub ({skill_id})\n---\n\n{md}"
224
+ with open(md_path, "w", encoding="utf-8") as f:
225
+ f.write(md + "\n")
226
+ else:
227
+ raw_zip = _download_skill_zip(skill_id)
228
+ try:
229
+ zf = zipfile.ZipFile(io.BytesIO(raw_zip))
230
+ except zipfile.BadZipFile as exc:
231
+ raise RuntimeError(
232
+ f"ClawHub returned invalid ZIP for '{skill_id}'."
233
+ ) from exc
234
+
235
+ for member in zf.namelist():
236
+ if member.startswith("__MACOSX") or member.startswith("."):
237
+ continue
238
+ dest = os.path.join(skill_dir, member)
239
+ if member.endswith("/"):
240
+ os.makedirs(dest, exist_ok=True)
241
+ else:
242
+ os.makedirs(os.path.dirname(dest), exist_ok=True)
243
+ with open(dest, "wb") as f:
244
+ f.write(zf.read(member))
245
+
246
+ if not os.path.exists(os.path.join(skill_dir, "SKILL.md")):
247
+ logger.warning("No SKILL.md found in ZIP for '%s'", skill_id)
248
+
249
+ source_url = f"{CLAWHUB_WEB}/skills/{skill_id}"
262
250
  meta_path = os.path.join(skill_dir, ".clawhub.json")
263
251
  with open(meta_path, "w", encoding="utf-8") as f:
264
- json.dump({"id": skill_id, "source": source_url, "installed_by": "pythonclaw"}, f, indent=2)
252
+ json.dump(
253
+ {"id": skill_id, "source": source_url, "installed_by": "pythonclaw"},
254
+ f, indent=2,
255
+ )
265
256
 
266
257
  return skill_dir
267
258
 
268
259
 
269
- def _derive_skill_name(skill_id: str, skill_md: str, detail: dict | None) -> str:
270
- """Extract a reasonable skill name from available data."""
271
- name_match = re.search(r"^name:\s*(.+)$", skill_md, re.MULTILINE)
272
- if name_match:
273
- return name_match.group(1).strip()
274
- if detail and detail.get("name"):
275
- return detail["name"]
276
- parts = skill_id.rsplit("-", 1)
277
- return parts[-1] if parts else skill_id
278
-
279
-
280
260
  def format_search_results(results: list[dict]) -> str:
281
261
  """Format search results for CLI display."""
282
262
  if not results:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonclaw
3
- Version: 0.6.4
3
+ Version: 0.6.5
4
4
  Summary: OpenClaw reimagined in pure Python — autonomous AI agent with memory, RAG, skills, web dashboard, and multi-channel support.
5
5
  Author-email: Eric Wang <wangchen2007915@gmail.com>
6
6
  License: MIT
File without changes
File without changes
File without changes
File without changes