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.
Files changed (112) hide show
  1. pythonclaw/__init__.py +17 -0
  2. pythonclaw/__main__.py +6 -0
  3. pythonclaw/channels/discord_bot.py +231 -0
  4. pythonclaw/channels/telegram_bot.py +236 -0
  5. pythonclaw/config.py +190 -0
  6. pythonclaw/core/__init__.py +25 -0
  7. pythonclaw/core/agent.py +773 -0
  8. pythonclaw/core/compaction.py +220 -0
  9. pythonclaw/core/knowledge/rag.py +93 -0
  10. pythonclaw/core/llm/anthropic_client.py +107 -0
  11. pythonclaw/core/llm/base.py +26 -0
  12. pythonclaw/core/llm/gemini_client.py +139 -0
  13. pythonclaw/core/llm/openai_compatible.py +39 -0
  14. pythonclaw/core/llm/response.py +57 -0
  15. pythonclaw/core/memory/manager.py +120 -0
  16. pythonclaw/core/memory/storage.py +164 -0
  17. pythonclaw/core/persistent_agent.py +103 -0
  18. pythonclaw/core/retrieval/__init__.py +6 -0
  19. pythonclaw/core/retrieval/chunker.py +78 -0
  20. pythonclaw/core/retrieval/dense.py +152 -0
  21. pythonclaw/core/retrieval/fusion.py +51 -0
  22. pythonclaw/core/retrieval/reranker.py +112 -0
  23. pythonclaw/core/retrieval/retriever.py +166 -0
  24. pythonclaw/core/retrieval/sparse.py +69 -0
  25. pythonclaw/core/session_store.py +269 -0
  26. pythonclaw/core/skill_loader.py +322 -0
  27. pythonclaw/core/skillhub.py +290 -0
  28. pythonclaw/core/tools.py +622 -0
  29. pythonclaw/core/utils.py +64 -0
  30. pythonclaw/daemon.py +221 -0
  31. pythonclaw/init.py +61 -0
  32. pythonclaw/main.py +489 -0
  33. pythonclaw/onboard.py +290 -0
  34. pythonclaw/scheduler/cron.py +310 -0
  35. pythonclaw/scheduler/heartbeat.py +178 -0
  36. pythonclaw/server.py +145 -0
  37. pythonclaw/session_manager.py +104 -0
  38. pythonclaw/templates/persona/demo_persona.md +2 -0
  39. pythonclaw/templates/skills/communication/CATEGORY.md +4 -0
  40. pythonclaw/templates/skills/communication/email/SKILL.md +54 -0
  41. pythonclaw/templates/skills/communication/email/__pycache__/send_email.cpython-311.pyc +0 -0
  42. pythonclaw/templates/skills/communication/email/send_email.py +88 -0
  43. pythonclaw/templates/skills/data/CATEGORY.md +4 -0
  44. pythonclaw/templates/skills/data/csv_analyzer/SKILL.md +51 -0
  45. pythonclaw/templates/skills/data/csv_analyzer/__pycache__/analyze.cpython-311.pyc +0 -0
  46. pythonclaw/templates/skills/data/csv_analyzer/analyze.py +138 -0
  47. pythonclaw/templates/skills/data/finance/SKILL.md +41 -0
  48. pythonclaw/templates/skills/data/finance/__pycache__/fetch_quote.cpython-311.pyc +0 -0
  49. pythonclaw/templates/skills/data/finance/fetch_quote.py +118 -0
  50. pythonclaw/templates/skills/data/news/SKILL.md +39 -0
  51. pythonclaw/templates/skills/data/news/__pycache__/search_news.cpython-311.pyc +0 -0
  52. pythonclaw/templates/skills/data/news/search_news.py +57 -0
  53. pythonclaw/templates/skills/data/pdf_reader/SKILL.md +40 -0
  54. pythonclaw/templates/skills/data/pdf_reader/__pycache__/read_pdf.cpython-311.pyc +0 -0
  55. pythonclaw/templates/skills/data/pdf_reader/read_pdf.py +113 -0
  56. pythonclaw/templates/skills/data/scraper/SKILL.md +39 -0
  57. pythonclaw/templates/skills/data/scraper/__pycache__/scrape.cpython-311.pyc +0 -0
  58. pythonclaw/templates/skills/data/scraper/scrape.py +92 -0
  59. pythonclaw/templates/skills/data/weather/SKILL.md +42 -0
  60. pythonclaw/templates/skills/data/weather/__pycache__/weather.cpython-311.pyc +0 -0
  61. pythonclaw/templates/skills/data/weather/weather.py +142 -0
  62. pythonclaw/templates/skills/data/youtube/SKILL.md +43 -0
  63. pythonclaw/templates/skills/data/youtube/__pycache__/youtube_info.cpython-311.pyc +0 -0
  64. pythonclaw/templates/skills/data/youtube/youtube_info.py +167 -0
  65. pythonclaw/templates/skills/dev/CATEGORY.md +4 -0
  66. pythonclaw/templates/skills/dev/code_runner/SKILL.md +46 -0
  67. pythonclaw/templates/skills/dev/code_runner/__pycache__/run_code.cpython-311.pyc +0 -0
  68. pythonclaw/templates/skills/dev/code_runner/run_code.py +117 -0
  69. pythonclaw/templates/skills/dev/github/SKILL.md +52 -0
  70. pythonclaw/templates/skills/dev/github/__pycache__/gh.cpython-311.pyc +0 -0
  71. pythonclaw/templates/skills/dev/github/gh.py +165 -0
  72. pythonclaw/templates/skills/dev/http_request/SKILL.md +40 -0
  73. pythonclaw/templates/skills/dev/http_request/__pycache__/request.cpython-311.pyc +0 -0
  74. pythonclaw/templates/skills/dev/http_request/request.py +90 -0
  75. pythonclaw/templates/skills/google/CATEGORY.md +4 -0
  76. pythonclaw/templates/skills/google/workspace/SKILL.md +98 -0
  77. pythonclaw/templates/skills/google/workspace/check_setup.sh +52 -0
  78. pythonclaw/templates/skills/meta/CATEGORY.md +4 -0
  79. pythonclaw/templates/skills/meta/skill_creator/SKILL.md +151 -0
  80. pythonclaw/templates/skills/system/CATEGORY.md +4 -0
  81. pythonclaw/templates/skills/system/change_persona/SKILL.md +41 -0
  82. pythonclaw/templates/skills/system/change_setting/SKILL.md +65 -0
  83. pythonclaw/templates/skills/system/change_setting/__pycache__/update_config.cpython-311.pyc +0 -0
  84. pythonclaw/templates/skills/system/change_setting/update_config.py +129 -0
  85. pythonclaw/templates/skills/system/change_soul/SKILL.md +41 -0
  86. pythonclaw/templates/skills/system/onboarding/SKILL.md +63 -0
  87. pythonclaw/templates/skills/system/onboarding/__pycache__/write_identity.cpython-311.pyc +0 -0
  88. pythonclaw/templates/skills/system/onboarding/write_identity.py +218 -0
  89. pythonclaw/templates/skills/system/random/SKILL.md +33 -0
  90. pythonclaw/templates/skills/system/random/__pycache__/random_util.cpython-311.pyc +0 -0
  91. pythonclaw/templates/skills/system/random/random_util.py +45 -0
  92. pythonclaw/templates/skills/system/time/SKILL.md +33 -0
  93. pythonclaw/templates/skills/system/time/__pycache__/time_util.cpython-311.pyc +0 -0
  94. pythonclaw/templates/skills/system/time/time_util.py +81 -0
  95. pythonclaw/templates/skills/text/CATEGORY.md +4 -0
  96. pythonclaw/templates/skills/text/translator/SKILL.md +47 -0
  97. pythonclaw/templates/skills/text/translator/__pycache__/translate.cpython-311.pyc +0 -0
  98. pythonclaw/templates/skills/text/translator/translate.py +66 -0
  99. pythonclaw/templates/skills/web/CATEGORY.md +4 -0
  100. pythonclaw/templates/skills/web/tavily/SKILL.md +61 -0
  101. pythonclaw/templates/soul/SOUL.md +54 -0
  102. pythonclaw/web/__init__.py +1 -0
  103. pythonclaw/web/app.py +585 -0
  104. pythonclaw/web/static/favicon.png +0 -0
  105. pythonclaw/web/static/index.html +1318 -0
  106. pythonclaw/web/static/logo.png +0 -0
  107. pythonclaw-0.2.0.dist-info/METADATA +410 -0
  108. pythonclaw-0.2.0.dist-info/RECORD +112 -0
  109. pythonclaw-0.2.0.dist-info/WHEEL +5 -0
  110. pythonclaw-0.2.0.dist-info/entry_points.txt +2 -0
  111. pythonclaw-0.2.0.dist-info/licenses/LICENSE +21 -0
  112. 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,4 @@
1
+ ---
2
+ name: google
3
+ description: Google Workspace skills — Gmail, Calendar, Drive, Contacts, Sheets, Docs via the gog CLI.
4
+ ---
@@ -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,4 @@
1
+ ---
2
+ name: meta
3
+ description: Meta-skills that extend the agent's own capabilities at runtime.
4
+ ---
@@ -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,4 @@
1
+ ---
2
+ name: system
3
+ description: Agent self-management — onboarding, identity, and configuration.
4
+ ---
@@ -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.