pythonclaw 0.2.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.
- pythonclaw/__init__.py +17 -0
- pythonclaw/__main__.py +6 -0
- pythonclaw/channels/discord_bot.py +231 -0
- pythonclaw/channels/telegram_bot.py +236 -0
- pythonclaw/config.py +190 -0
- pythonclaw/core/__init__.py +25 -0
- pythonclaw/core/agent.py +773 -0
- pythonclaw/core/compaction.py +220 -0
- pythonclaw/core/knowledge/rag.py +93 -0
- pythonclaw/core/llm/anthropic_client.py +107 -0
- pythonclaw/core/llm/base.py +26 -0
- pythonclaw/core/llm/gemini_client.py +139 -0
- pythonclaw/core/llm/openai_compatible.py +39 -0
- pythonclaw/core/llm/response.py +57 -0
- pythonclaw/core/memory/manager.py +120 -0
- pythonclaw/core/memory/storage.py +164 -0
- pythonclaw/core/persistent_agent.py +103 -0
- pythonclaw/core/retrieval/__init__.py +6 -0
- pythonclaw/core/retrieval/chunker.py +78 -0
- pythonclaw/core/retrieval/dense.py +152 -0
- pythonclaw/core/retrieval/fusion.py +51 -0
- pythonclaw/core/retrieval/reranker.py +112 -0
- pythonclaw/core/retrieval/retriever.py +166 -0
- pythonclaw/core/retrieval/sparse.py +69 -0
- pythonclaw/core/session_store.py +269 -0
- pythonclaw/core/skill_loader.py +322 -0
- pythonclaw/core/skillhub.py +290 -0
- pythonclaw/core/tools.py +622 -0
- pythonclaw/core/utils.py +64 -0
- pythonclaw/daemon.py +221 -0
- pythonclaw/init.py +61 -0
- pythonclaw/main.py +489 -0
- pythonclaw/onboard.py +290 -0
- pythonclaw/scheduler/cron.py +310 -0
- pythonclaw/scheduler/heartbeat.py +178 -0
- pythonclaw/server.py +145 -0
- pythonclaw/session_manager.py +104 -0
- pythonclaw/templates/persona/demo_persona.md +2 -0
- pythonclaw/templates/skills/communication/CATEGORY.md +4 -0
- pythonclaw/templates/skills/communication/email/SKILL.md +54 -0
- pythonclaw/templates/skills/communication/email/__pycache__/send_email.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/communication/email/send_email.py +88 -0
- pythonclaw/templates/skills/data/CATEGORY.md +4 -0
- pythonclaw/templates/skills/data/csv_analyzer/SKILL.md +51 -0
- pythonclaw/templates/skills/data/csv_analyzer/__pycache__/analyze.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/data/csv_analyzer/analyze.py +138 -0
- pythonclaw/templates/skills/data/finance/SKILL.md +41 -0
- pythonclaw/templates/skills/data/finance/__pycache__/fetch_quote.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/data/finance/fetch_quote.py +118 -0
- pythonclaw/templates/skills/data/news/SKILL.md +39 -0
- pythonclaw/templates/skills/data/news/__pycache__/search_news.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/data/news/search_news.py +57 -0
- pythonclaw/templates/skills/data/pdf_reader/SKILL.md +40 -0
- pythonclaw/templates/skills/data/pdf_reader/__pycache__/read_pdf.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/data/pdf_reader/read_pdf.py +113 -0
- pythonclaw/templates/skills/data/scraper/SKILL.md +39 -0
- pythonclaw/templates/skills/data/scraper/__pycache__/scrape.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/data/scraper/scrape.py +92 -0
- pythonclaw/templates/skills/data/weather/SKILL.md +42 -0
- pythonclaw/templates/skills/data/weather/__pycache__/weather.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/data/weather/weather.py +142 -0
- pythonclaw/templates/skills/data/youtube/SKILL.md +43 -0
- pythonclaw/templates/skills/data/youtube/__pycache__/youtube_info.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/data/youtube/youtube_info.py +167 -0
- pythonclaw/templates/skills/dev/CATEGORY.md +4 -0
- pythonclaw/templates/skills/dev/code_runner/SKILL.md +46 -0
- pythonclaw/templates/skills/dev/code_runner/__pycache__/run_code.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/dev/code_runner/run_code.py +117 -0
- pythonclaw/templates/skills/dev/github/SKILL.md +52 -0
- pythonclaw/templates/skills/dev/github/__pycache__/gh.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/dev/github/gh.py +165 -0
- pythonclaw/templates/skills/dev/http_request/SKILL.md +40 -0
- pythonclaw/templates/skills/dev/http_request/__pycache__/request.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/dev/http_request/request.py +90 -0
- pythonclaw/templates/skills/google/CATEGORY.md +4 -0
- pythonclaw/templates/skills/google/workspace/SKILL.md +98 -0
- pythonclaw/templates/skills/google/workspace/check_setup.sh +52 -0
- pythonclaw/templates/skills/meta/CATEGORY.md +4 -0
- pythonclaw/templates/skills/meta/skill_creator/SKILL.md +151 -0
- pythonclaw/templates/skills/system/CATEGORY.md +4 -0
- pythonclaw/templates/skills/system/change_persona/SKILL.md +41 -0
- pythonclaw/templates/skills/system/change_setting/SKILL.md +65 -0
- pythonclaw/templates/skills/system/change_setting/__pycache__/update_config.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/system/change_setting/update_config.py +129 -0
- pythonclaw/templates/skills/system/change_soul/SKILL.md +41 -0
- pythonclaw/templates/skills/system/onboarding/SKILL.md +63 -0
- pythonclaw/templates/skills/system/onboarding/__pycache__/write_identity.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/system/onboarding/write_identity.py +218 -0
- pythonclaw/templates/skills/system/random/SKILL.md +33 -0
- pythonclaw/templates/skills/system/random/__pycache__/random_util.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/system/random/random_util.py +45 -0
- pythonclaw/templates/skills/system/time/SKILL.md +33 -0
- pythonclaw/templates/skills/system/time/__pycache__/time_util.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/system/time/time_util.py +81 -0
- pythonclaw/templates/skills/text/CATEGORY.md +4 -0
- pythonclaw/templates/skills/text/translator/SKILL.md +47 -0
- pythonclaw/templates/skills/text/translator/__pycache__/translate.cpython-311.pyc +0 -0
- pythonclaw/templates/skills/text/translator/translate.py +66 -0
- pythonclaw/templates/skills/web/CATEGORY.md +4 -0
- pythonclaw/templates/skills/web/tavily/SKILL.md +61 -0
- pythonclaw/templates/soul/SOUL.md +54 -0
- pythonclaw/web/__init__.py +1 -0
- pythonclaw/web/app.py +585 -0
- pythonclaw/web/static/favicon.png +0 -0
- pythonclaw/web/static/index.html +1318 -0
- pythonclaw/web/static/logo.png +0 -0
- pythonclaw-0.2.0.dist-info/METADATA +410 -0
- pythonclaw-0.2.0.dist-info/RECORD +112 -0
- pythonclaw-0.2.0.dist-info/WHEEL +5 -0
- pythonclaw-0.2.0.dist-info/entry_points.txt +2 -0
- pythonclaw-0.2.0.dist-info/licenses/LICENSE +21 -0
- pythonclaw-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""GitHub API client. Reads token from pythonclaw.json."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import requests
|
|
12
|
+
except ImportError:
|
|
13
|
+
print("Error: requests not installed. Run: pip install requests", file=sys.stderr)
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
|
|
16
|
+
API = "https://api.github.com"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _load_token() -> str:
|
|
20
|
+
for path in ["pythonclaw.json", os.path.expanduser("~/.pythonclaw/pythonclaw.json")]:
|
|
21
|
+
if not os.path.isfile(path):
|
|
22
|
+
continue
|
|
23
|
+
with open(path, encoding="utf-8") as f:
|
|
24
|
+
text = f.read()
|
|
25
|
+
text = re.sub(r'//.*$', '', text, flags=re.MULTILINE)
|
|
26
|
+
text = re.sub(r',\s*([}\]])', r'\1', text)
|
|
27
|
+
data = json.loads(text)
|
|
28
|
+
token = data.get("skills", {}).get("github", {}).get("token", "")
|
|
29
|
+
if token:
|
|
30
|
+
return token
|
|
31
|
+
return ""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _headers() -> dict:
|
|
35
|
+
token = _load_token()
|
|
36
|
+
h = {"Accept": "application/vnd.github+json"}
|
|
37
|
+
if token:
|
|
38
|
+
h["Authorization"] = f"Bearer {token}"
|
|
39
|
+
return h
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _get(path: str) -> dict | list:
|
|
43
|
+
resp = requests.get(f"{API}{path}", headers=_headers(), timeout=30)
|
|
44
|
+
resp.raise_for_status()
|
|
45
|
+
return resp.json()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _post(path: str, body: dict) -> dict:
|
|
49
|
+
resp = requests.post(f"{API}{path}", headers=_headers(), json=body, timeout=30)
|
|
50
|
+
resp.raise_for_status()
|
|
51
|
+
return resp.json()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def cmd_repos(user: str) -> None:
|
|
55
|
+
repos = _get(f"/users/{user}/repos?sort=updated&per_page=30")
|
|
56
|
+
for r in repos:
|
|
57
|
+
stars = r.get("stargazers_count", 0)
|
|
58
|
+
lang = r.get("language", "")
|
|
59
|
+
desc = r.get("description", "") or ""
|
|
60
|
+
print(f" {r['full_name']} [{lang}] \u2605{stars}")
|
|
61
|
+
if desc:
|
|
62
|
+
print(f" {desc[:100]}")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def cmd_repo(full_name: str) -> None:
|
|
66
|
+
r = _get(f"/repos/{full_name}")
|
|
67
|
+
print(json.dumps({
|
|
68
|
+
"name": r["full_name"],
|
|
69
|
+
"description": r.get("description", ""),
|
|
70
|
+
"language": r.get("language"),
|
|
71
|
+
"stars": r["stargazers_count"],
|
|
72
|
+
"forks": r["forks_count"],
|
|
73
|
+
"open_issues": r["open_issues_count"],
|
|
74
|
+
"url": r["html_url"],
|
|
75
|
+
"created": r["created_at"],
|
|
76
|
+
"updated": r["updated_at"],
|
|
77
|
+
}, indent=2))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def cmd_issues(full_name: str) -> None:
|
|
81
|
+
issues = _get(f"/repos/{full_name}/issues?state=open&per_page=30")
|
|
82
|
+
for i in issues:
|
|
83
|
+
if i.get("pull_request"):
|
|
84
|
+
continue
|
|
85
|
+
labels = ", ".join(lb["name"] for lb in i.get("labels", []))
|
|
86
|
+
print(f" #{i['number']} {i['title']}")
|
|
87
|
+
if labels:
|
|
88
|
+
print(f" Labels: {labels}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def cmd_create_issue(full_name: str, title: str, body: str) -> None:
|
|
92
|
+
result = _post(f"/repos/{full_name}/issues", {"title": title, "body": body})
|
|
93
|
+
print(f"Created issue #{result['number']}: {result['html_url']}")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def cmd_prs(full_name: str) -> None:
|
|
97
|
+
prs = _get(f"/repos/{full_name}/pulls?state=open&per_page=30")
|
|
98
|
+
for pr in prs:
|
|
99
|
+
print(f" #{pr['number']} {pr['title']} ({pr['user']['login']})")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def cmd_pr(full_name: str, number: int) -> None:
|
|
103
|
+
pr = _get(f"/repos/{full_name}/pulls/{number}")
|
|
104
|
+
print(json.dumps({
|
|
105
|
+
"number": pr["number"],
|
|
106
|
+
"title": pr["title"],
|
|
107
|
+
"state": pr["state"],
|
|
108
|
+
"author": pr["user"]["login"],
|
|
109
|
+
"branch": pr["head"]["ref"],
|
|
110
|
+
"base": pr["base"]["ref"],
|
|
111
|
+
"body": (pr.get("body") or "")[:500],
|
|
112
|
+
"url": pr["html_url"],
|
|
113
|
+
"mergeable": pr.get("mergeable"),
|
|
114
|
+
}, indent=2))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main():
|
|
118
|
+
parser = argparse.ArgumentParser(description="GitHub API client.")
|
|
119
|
+
sub = parser.add_subparsers(dest="command")
|
|
120
|
+
|
|
121
|
+
p_repos = sub.add_parser("repos")
|
|
122
|
+
p_repos.add_argument("user")
|
|
123
|
+
|
|
124
|
+
p_repo = sub.add_parser("repo")
|
|
125
|
+
p_repo.add_argument("full_name", help="owner/repo")
|
|
126
|
+
|
|
127
|
+
p_issues = sub.add_parser("issues")
|
|
128
|
+
p_issues.add_argument("full_name", help="owner/repo")
|
|
129
|
+
|
|
130
|
+
p_ci = sub.add_parser("create-issue")
|
|
131
|
+
p_ci.add_argument("full_name", help="owner/repo")
|
|
132
|
+
p_ci.add_argument("--title", required=True)
|
|
133
|
+
p_ci.add_argument("--body", default="")
|
|
134
|
+
|
|
135
|
+
p_prs = sub.add_parser("prs")
|
|
136
|
+
p_prs.add_argument("full_name", help="owner/repo")
|
|
137
|
+
|
|
138
|
+
p_pr = sub.add_parser("pr")
|
|
139
|
+
p_pr.add_argument("full_name", help="owner/repo")
|
|
140
|
+
p_pr.add_argument("number", type=int)
|
|
141
|
+
|
|
142
|
+
args = parser.parse_args()
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
if args.command == "repos":
|
|
146
|
+
cmd_repos(args.user)
|
|
147
|
+
elif args.command == "repo":
|
|
148
|
+
cmd_repo(args.full_name)
|
|
149
|
+
elif args.command == "issues":
|
|
150
|
+
cmd_issues(args.full_name)
|
|
151
|
+
elif args.command == "create-issue":
|
|
152
|
+
cmd_create_issue(args.full_name, args.title, args.body)
|
|
153
|
+
elif args.command == "prs":
|
|
154
|
+
cmd_prs(args.full_name)
|
|
155
|
+
elif args.command == "pr":
|
|
156
|
+
cmd_pr(args.full_name, args.number)
|
|
157
|
+
else:
|
|
158
|
+
parser.print_help()
|
|
159
|
+
except requests.HTTPError as exc:
|
|
160
|
+
print(f"GitHub API error: {exc.response.status_code} {exc.response.text[:200]}", file=sys.stderr)
|
|
161
|
+
sys.exit(1)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
main()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: http_request
|
|
3
|
+
description: >
|
|
4
|
+
Make HTTP requests (GET, POST, PUT, DELETE, PATCH) to any API endpoint.
|
|
5
|
+
Use when the user asks to call an API, test an endpoint, fetch JSON data
|
|
6
|
+
from a URL, or interact with a REST API.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Instructions
|
|
10
|
+
|
|
11
|
+
Make arbitrary HTTP requests to any API endpoint.
|
|
12
|
+
|
|
13
|
+
### Prerequisites
|
|
14
|
+
|
|
15
|
+
Install dependency: `pip install requests`
|
|
16
|
+
|
|
17
|
+
### Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
python {skill_path}/request.py URL [options]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
- `--method GET|POST|PUT|DELETE|PATCH` (default: GET)
|
|
25
|
+
- `--data '{"key": "value"}'` — JSON request body
|
|
26
|
+
- `--header "Name: Value"` — custom header (repeatable)
|
|
27
|
+
- `--timeout 30` — timeout in seconds
|
|
28
|
+
- `--format text|json|headers` — output format
|
|
29
|
+
|
|
30
|
+
### Examples
|
|
31
|
+
|
|
32
|
+
- "GET https://api.github.com/users/octocat"
|
|
33
|
+
- "POST to https://httpbin.org/post with body {'name': 'test'}"
|
|
34
|
+
- "Call the weather API at https://api.example.com/weather?city=Tokyo"
|
|
35
|
+
|
|
36
|
+
## Resources
|
|
37
|
+
|
|
38
|
+
| File | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `request.py` | Generic HTTP request tool |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Make arbitrary HTTP requests to any API endpoint."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import requests
|
|
10
|
+
except ImportError:
|
|
11
|
+
print("Error: requests not installed. Run: pip install requests", file=sys.stderr)
|
|
12
|
+
sys.exit(1)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def http_request(
|
|
16
|
+
url: str,
|
|
17
|
+
method: str = "GET",
|
|
18
|
+
data: str | None = None,
|
|
19
|
+
headers: dict | None = None,
|
|
20
|
+
timeout: int = 30,
|
|
21
|
+
) -> dict:
|
|
22
|
+
headers = headers or {}
|
|
23
|
+
body = None
|
|
24
|
+
if data:
|
|
25
|
+
try:
|
|
26
|
+
body = json.loads(data)
|
|
27
|
+
headers.setdefault("Content-Type", "application/json")
|
|
28
|
+
except json.JSONDecodeError:
|
|
29
|
+
body = data
|
|
30
|
+
|
|
31
|
+
resp = requests.request(
|
|
32
|
+
method=method.upper(),
|
|
33
|
+
url=url,
|
|
34
|
+
json=body if isinstance(body, (dict, list)) else None,
|
|
35
|
+
data=body if isinstance(body, str) else None,
|
|
36
|
+
headers=headers,
|
|
37
|
+
timeout=timeout,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
resp_body = resp.json()
|
|
42
|
+
except Exception:
|
|
43
|
+
resp_body = resp.text[:5000]
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
"status": resp.status_code,
|
|
47
|
+
"headers": dict(resp.headers),
|
|
48
|
+
"body": resp_body,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main():
|
|
53
|
+
parser = argparse.ArgumentParser(description="Make HTTP requests.")
|
|
54
|
+
parser.add_argument("url", help="Request URL")
|
|
55
|
+
parser.add_argument("--method", default="GET", choices=["GET", "POST", "PUT", "DELETE", "PATCH"])
|
|
56
|
+
parser.add_argument("--data", default=None, help="Request body (JSON string)")
|
|
57
|
+
parser.add_argument("--header", action="append", default=[], help="Header in 'Name: Value' format")
|
|
58
|
+
parser.add_argument("--timeout", type=int, default=30)
|
|
59
|
+
parser.add_argument("--format", choices=["text", "json", "headers"], default="text")
|
|
60
|
+
args = parser.parse_args()
|
|
61
|
+
|
|
62
|
+
headers = {}
|
|
63
|
+
for h in args.header:
|
|
64
|
+
if ":" in h:
|
|
65
|
+
k, v = h.split(":", 1)
|
|
66
|
+
headers[k.strip()] = v.strip()
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
result = http_request(args.url, args.method, args.data, headers, args.timeout)
|
|
70
|
+
except Exception as exc:
|
|
71
|
+
print(f"Request failed: {exc}", file=sys.stderr)
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
|
|
74
|
+
if args.format == "json":
|
|
75
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
76
|
+
elif args.format == "headers":
|
|
77
|
+
print(f"Status: {result['status']}")
|
|
78
|
+
for k, v in result["headers"].items():
|
|
79
|
+
print(f" {k}: {v}")
|
|
80
|
+
else:
|
|
81
|
+
print(f"Status: {result['status']}")
|
|
82
|
+
body = result["body"]
|
|
83
|
+
if isinstance(body, (dict, list)):
|
|
84
|
+
print(json.dumps(body, indent=2, ensure_ascii=False))
|
|
85
|
+
else:
|
|
86
|
+
print(body)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
main()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: google_workspace
|
|
3
|
+
description: >
|
|
4
|
+
Google Workspace CLI — Gmail, Calendar, Drive, Contacts, Sheets, and Docs
|
|
5
|
+
via the gog command-line tool. Use when the user asks to send email, search
|
|
6
|
+
Gmail, check calendar events, manage Google Drive files, read or edit
|
|
7
|
+
Google Sheets, export Google Docs, or look up contacts.
|
|
8
|
+
---
|
|
9
|
+
# Google Workspace (gog CLI)
|
|
10
|
+
|
|
11
|
+
Powered by [gog](https://github.com/steipete/gogcli) — a Google Workspace
|
|
12
|
+
CLI for Gmail, Calendar, Drive, Contacts, Sheets, and Docs.
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
Install gog:
|
|
17
|
+
```
|
|
18
|
+
brew install steipete/tap/gogcli
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
On Linux (manual install):
|
|
22
|
+
```
|
|
23
|
+
curl -fsSL https://api.github.com/repos/steipete/gogcli/releases/latest \
|
|
24
|
+
| grep browser_download_url | grep linux_amd64
|
|
25
|
+
# Download the tarball, extract, and install:
|
|
26
|
+
# sudo install -m 0755 gog /usr/local/bin/gog
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Setup (one-time)
|
|
30
|
+
|
|
31
|
+
1. Create OAuth credentials at [Google Cloud Console](https://console.cloud.google.com/apis/credentials)
|
|
32
|
+
(Desktop App type) and download `client_secret.json`.
|
|
33
|
+
2. Run:
|
|
34
|
+
```
|
|
35
|
+
gog auth credentials /path/to/client_secret.json
|
|
36
|
+
gog auth add you@gmail.com --services gmail,calendar,drive,contacts,sheets,docs
|
|
37
|
+
```
|
|
38
|
+
3. Optionally set `GOG_ACCOUNT=you@gmail.com` as an environment variable to avoid `--account` on every call.
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
### Gmail
|
|
43
|
+
|
|
44
|
+
| Action | Command |
|
|
45
|
+
|--------|---------|
|
|
46
|
+
| Search mail | `gog gmail search 'newer_than:7d' --max 10` |
|
|
47
|
+
| Send email | `gog gmail send --to user@example.com --subject "Hi" --body "Hello"` |
|
|
48
|
+
| Read message | `gog gmail read <message_id>` |
|
|
49
|
+
| Mark as read | `gog gmail labels modify <message_id> --remove UNREAD` |
|
|
50
|
+
| List labels | `gog gmail labels list` |
|
|
51
|
+
|
|
52
|
+
### Calendar
|
|
53
|
+
|
|
54
|
+
| Action | Command |
|
|
55
|
+
|--------|---------|
|
|
56
|
+
| List events | `gog calendar events --from 2026-02-23 --to 2026-02-28` |
|
|
57
|
+
| Create event | `gog calendar create --title "Meeting" --start "2026-02-24T10:00" --end "2026-02-24T11:00"` |
|
|
58
|
+
|
|
59
|
+
### Drive
|
|
60
|
+
|
|
61
|
+
| Action | Command |
|
|
62
|
+
|--------|---------|
|
|
63
|
+
| Search files | `gog drive search "query" --max 10` |
|
|
64
|
+
| List files | `gog drive list --max 20` |
|
|
65
|
+
| Download | `gog drive download <file_id> --out /tmp/file.pdf` |
|
|
66
|
+
|
|
67
|
+
### Google Sheets
|
|
68
|
+
|
|
69
|
+
| Action | Command |
|
|
70
|
+
|--------|---------|
|
|
71
|
+
| Get data | `gog sheets get <spreadsheet_id> "Sheet1!A1:D10" --json` |
|
|
72
|
+
| Metadata | `gog sheets metadata <spreadsheet_id> --json` |
|
|
73
|
+
| Append rows | `gog sheets append <spreadsheet_id> "Sheet1!A:C" --values-json '[["x","y","z"]]' --insert I...
|
|
74
|
+
| Update cells | `gog sheets update <spreadsheet_id> "Sheet1!A1:B2" --values-json '[["A","B"],["1","2"]]' -...
|
|
75
|
+
| Clear range | `gog sheets clear <spreadsheet_id> "Sheet1!A2:Z"` |
|
|
76
|
+
|
|
77
|
+
### Google Docs
|
|
78
|
+
|
|
79
|
+
| Action | Command |
|
|
80
|
+
|--------|---------|
|
|
81
|
+
| Read content | `gog docs cat <document_id>` |
|
|
82
|
+
| Export | `gog docs export <document_id> --format txt --out /tmp/doc.txt` |
|
|
83
|
+
| Copy | `gog docs copy <document_id> --title "Copy of Doc"` |
|
|
84
|
+
|
|
85
|
+
### Contacts
|
|
86
|
+
|
|
87
|
+
| Action | Command |
|
|
88
|
+
|--------|---------|
|
|
89
|
+
| List contacts | `gog contacts list --max 20` |
|
|
90
|
+
| Search | `gog contacts search "name"` |
|
|
91
|
+
|
|
92
|
+
## Important Notes
|
|
93
|
+
|
|
94
|
+
- **Always confirm** before sending emails or creating calendar events.
|
|
95
|
+
- For scripting/automation, prefer `--json` and `--no-input` flags.
|
|
96
|
+
- Google Docs in-place editing requires the Docs API directly (not available in gog).
|
|
97
|
+
- Set `GOG_ACCOUNT` env var to skip `--account` on every command.
|
|
98
|
+
- Use `gog auth list` to check configured accounts.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pre-activation check for the Google Workspace (gog) skill.
|
|
3
|
+
# Exit 0 = ready, exit 1 = not ready (output tells the user what to fix).
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
# ?? 1. Is gog installed? ??????????????????????????????????????????????????????
|
|
8
|
+
if ! command -v gog &>/dev/null; then
|
|
9
|
+
cat <<'EOF'
|
|
10
|
+
ERROR: The 'gog' CLI is not installed.
|
|
11
|
+
|
|
12
|
+
To install on macOS:
|
|
13
|
+
brew install steipete/tap/gogcli
|
|
14
|
+
|
|
15
|
+
To install on Linux:
|
|
16
|
+
curl -fsSL https://api.github.com/repos/steipete/gogcli/releases/latest \
|
|
17
|
+
| grep browser_download_url | grep linux_amd64
|
|
18
|
+
# Download the tarball, extract, then: sudo install -m 0755 gog /usr/local/bin/gog
|
|
19
|
+
|
|
20
|
+
More info: https://github.com/steipete/gogcli
|
|
21
|
+
EOF
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
echo "gog version: $(gog --version 2>/dev/null || echo 'unknown')"
|
|
26
|
+
|
|
27
|
+
# ?? 2. Is at least one account configured? ????????????????????????????????????
|
|
28
|
+
AUTH_OUTPUT=$(gog auth list 2>&1)
|
|
29
|
+
if echo "$AUTH_OUTPUT" | grep -qi "no tokens"; then
|
|
30
|
+
cat <<'EOF'
|
|
31
|
+
|
|
32
|
+
ERROR: No Google account is configured in gog.
|
|
33
|
+
|
|
34
|
+
Setup steps:
|
|
35
|
+
1. Go to https://console.cloud.google.com/apis/credentials
|
|
36
|
+
2. Create an OAuth 2.0 Client ID (Desktop App type)
|
|
37
|
+
3. Download the client_secret.json file
|
|
38
|
+
4. Run:
|
|
39
|
+
gog auth credentials /path/to/client_secret.json
|
|
40
|
+
gog auth add you@gmail.com --services gmail,calendar,drive,contacts,sheets,docs
|
|
41
|
+
5. A browser window will open ? authorize the app.
|
|
42
|
+
|
|
43
|
+
After setup, try again.
|
|
44
|
+
EOF
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
echo ""
|
|
49
|
+
echo "Configured accounts:"
|
|
50
|
+
echo "$AUTH_OUTPUT"
|
|
51
|
+
echo ""
|
|
52
|
+
echo "Ready to use Google Workspace commands."
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill_creator
|
|
3
|
+
description: >
|
|
4
|
+
Dynamically create new skills when no existing skill can handle the user's
|
|
5
|
+
request. Use this when you need a capability that doesn't exist yet —
|
|
6
|
+
write the code, bundle it as a skill, install dependencies, and make it
|
|
7
|
+
immediately available.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Instructions
|
|
11
|
+
|
|
12
|
+
You have the ability to **create brand-new skills on the fly** using the
|
|
13
|
+
`create_skill` tool. This is your "god mode" — if no existing skill can
|
|
14
|
+
fulfill a request, design and build one yourself.
|
|
15
|
+
|
|
16
|
+
### When to Use
|
|
17
|
+
|
|
18
|
+
- The user asks for something none of the installed skills cover
|
|
19
|
+
- An existing skill is too limited and a better replacement is needed
|
|
20
|
+
- A recurring task would benefit from a dedicated, reusable skill
|
|
21
|
+
|
|
22
|
+
### When NOT to Use
|
|
23
|
+
|
|
24
|
+
- **DON'T** create a skill for a one-off task that existing tools can handle
|
|
25
|
+
(e.g. `run_command` can already execute shell commands)
|
|
26
|
+
- **DON'T** create a skill that's too specific to be reused — always think
|
|
27
|
+
about what the **general category** of the request is and build for that
|
|
28
|
+
- **DON'T** hardcode user-specific values (names, URLs, topics) into the
|
|
29
|
+
skill — make them parameters
|
|
30
|
+
|
|
31
|
+
### Design Principles
|
|
32
|
+
|
|
33
|
+
1. **GENERIC over specific** — NEVER create a skill that only works for
|
|
34
|
+
one narrow task. Always generalize:
|
|
35
|
+
- BAD: `us_iran_news_fetcher` (hardcoded topic, useless for anything else)
|
|
36
|
+
- GOOD: `news` (searches any topic, parameterized query)
|
|
37
|
+
- BAD: `send_meeting_invite_to_bob` (single use case)
|
|
38
|
+
- GOOD: `email` (sends any email to any recipient)
|
|
39
|
+
2. **Parameterized** — use command-line arguments, not hardcoded values.
|
|
40
|
+
Every specific detail (query, recipient, URL, etc.) should be an argument.
|
|
41
|
+
3. **Single Responsibility** — each skill should do one thing well
|
|
42
|
+
4. **Clean Code** — write production-quality Python scripts with proper
|
|
43
|
+
error handling, logging, and docstrings
|
|
44
|
+
5. **Minimal Dependencies** — only add pip packages that are truly needed
|
|
45
|
+
6. **Clear Instructions** — the SKILL.md body should explain exactly how
|
|
46
|
+
to use the skill so your future self (or another agent) can follow it
|
|
47
|
+
7. **Reusable** — design the skill to work for ANY similar future request.
|
|
48
|
+
Ask yourself: "Would this skill be useful to someone with a completely
|
|
49
|
+
different task?" If not, generalize it.
|
|
50
|
+
8. **Config-driven credentials** — if the skill needs API keys or tokens,
|
|
51
|
+
read them from `pythonclaw.json` (under `skills.<name>`) instead of
|
|
52
|
+
hardcoding or requiring environment variables
|
|
53
|
+
|
|
54
|
+
### Step-by-Step Workflow
|
|
55
|
+
|
|
56
|
+
1. **Analyze the gap**: identify exactly what capability is missing
|
|
57
|
+
2. **Plan the skill**: decide on the name, category, required scripts,
|
|
58
|
+
and any pip dependencies
|
|
59
|
+
3. **Call `create_skill`** with:
|
|
60
|
+
- `name` — short, descriptive, snake_case (e.g. `pdf_summarizer`)
|
|
61
|
+
- `description` — one-line summary for the skill catalog
|
|
62
|
+
- `instructions` — full Markdown body (see template below)
|
|
63
|
+
- `category` — group folder (e.g. `data`, `dev`, `web`, `automation`)
|
|
64
|
+
- `resources` — dict mapping filenames to their source code
|
|
65
|
+
- `dependencies` — list of pip packages to install
|
|
66
|
+
4. **Activate the skill**: call `use_skill(skill_name="<name>")` to load
|
|
67
|
+
the new instructions
|
|
68
|
+
5. **Run it**: follow the loaded instructions to execute the task
|
|
69
|
+
|
|
70
|
+
### SKILL.md Body Template
|
|
71
|
+
|
|
72
|
+
Use this structure for the `instructions` argument:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
## Instructions
|
|
76
|
+
|
|
77
|
+
<Clear explanation of what the skill does and when to use it.>
|
|
78
|
+
|
|
79
|
+
### Prerequisites
|
|
80
|
+
|
|
81
|
+
- <any setup steps or API keys needed>
|
|
82
|
+
|
|
83
|
+
### Usage
|
|
84
|
+
|
|
85
|
+
1. <step-by-step usage instructions>
|
|
86
|
+
2. Call `run_command` with: `python context/skills/<category>/<name>/<script>.py <args>`
|
|
87
|
+
3. <interpret results>
|
|
88
|
+
|
|
89
|
+
### Examples
|
|
90
|
+
|
|
91
|
+
**Example:** <describe a typical use case>
|
|
92
|
+
|
|
93
|
+
## Resources
|
|
94
|
+
|
|
95
|
+
| File | Description |
|
|
96
|
+
|------|-------------|
|
|
97
|
+
| `script.py` | <what it does> |
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Resource Script Template
|
|
101
|
+
|
|
102
|
+
When writing Python scripts for `resources`, follow this pattern:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
#!/usr/bin/env python3
|
|
106
|
+
"""One-line description of the script."""
|
|
107
|
+
import argparse
|
|
108
|
+
import json
|
|
109
|
+
import sys
|
|
110
|
+
|
|
111
|
+
def main():
|
|
112
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
113
|
+
parser.add_argument("input", help="Input to process")
|
|
114
|
+
parser.add_argument("--format", default="text", choices=["text", "json"])
|
|
115
|
+
args = parser.parse_args()
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
result = process(args.input)
|
|
119
|
+
if args.format == "json":
|
|
120
|
+
print(json.dumps(result, indent=2))
|
|
121
|
+
else:
|
|
122
|
+
print(result)
|
|
123
|
+
except Exception as exc:
|
|
124
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
|
|
127
|
+
def process(data):
|
|
128
|
+
# ... actual logic ...
|
|
129
|
+
return data
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
main()
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Example: Creating a CSV Analyzer Skill
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
create_skill(
|
|
139
|
+
name="csv_analyzer",
|
|
140
|
+
description="Analyze CSV files — summary statistics, column info, and data preview.",
|
|
141
|
+
category="data",
|
|
142
|
+
instructions="## Instructions\n\nAnalyze CSV files ...",
|
|
143
|
+
resources={
|
|
144
|
+
"analyze.py": "#!/usr/bin/env python3\nimport pandas as pd\n..."
|
|
145
|
+
},
|
|
146
|
+
dependencies=["pandas"]
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
After creation, call `use_skill(skill_name="csv_analyzer")` to activate it,
|
|
151
|
+
then follow its instructions.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: change_persona
|
|
3
|
+
description: >
|
|
4
|
+
Modify the agent's personality and role (persona.md). Use when the user
|
|
5
|
+
wants to change the agent's personality, communication style, focus area,
|
|
6
|
+
or specialization.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Instructions
|
|
10
|
+
|
|
11
|
+
Modify the agent's persona file at `context/persona/persona.md`.
|
|
12
|
+
|
|
13
|
+
### When to Use
|
|
14
|
+
|
|
15
|
+
- User says "be more formal", "be funnier", "focus on finance now"
|
|
16
|
+
- User wants to change the agent's specialization or expertise area
|
|
17
|
+
- User asks to adjust communication style or personality traits
|
|
18
|
+
|
|
19
|
+
### How to Use
|
|
20
|
+
|
|
21
|
+
1. Ask the user what they want to change
|
|
22
|
+
2. Read the current persona file:
|
|
23
|
+
```
|
|
24
|
+
read_file("context/persona/persona.md")
|
|
25
|
+
```
|
|
26
|
+
3. Modify the relevant section and write it back:
|
|
27
|
+
```
|
|
28
|
+
write_file("context/persona/persona.md", "...updated content...")
|
|
29
|
+
```
|
|
30
|
+
4. Tell the user: "Persona updated. Use `/clear` to apply the changes
|
|
31
|
+
in a fresh conversation, or they will take effect on next restart."
|
|
32
|
+
|
|
33
|
+
### Important
|
|
34
|
+
|
|
35
|
+
- Preserve the overall structure of persona.md
|
|
36
|
+
- Only change the specific section the user asked about
|
|
37
|
+
- If the file doesn't exist yet, create it with a reasonable template
|
|
38
|
+
|
|
39
|
+
## Resources
|
|
40
|
+
|
|
41
|
+
This skill uses the built-in `read_file` and `write_file` tools directly.
|