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,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: change_setting
|
|
3
|
+
description: >
|
|
4
|
+
Modify pythonclaw.json configuration at runtime. Use when the user wants
|
|
5
|
+
to set API keys, tokens, change LLM provider, adjust web port, or
|
|
6
|
+
update any configuration value.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Instructions
|
|
10
|
+
|
|
11
|
+
Read and modify the project configuration file `pythonclaw.json`.
|
|
12
|
+
|
|
13
|
+
### When to Use
|
|
14
|
+
|
|
15
|
+
- User says "set my API key to ...", "change provider to ...",
|
|
16
|
+
"update my token", "configure email", etc.
|
|
17
|
+
- User wants to change any runtime setting without editing files manually
|
|
18
|
+
|
|
19
|
+
### How to Use
|
|
20
|
+
|
|
21
|
+
1. Read the current config:
|
|
22
|
+
```bash
|
|
23
|
+
python {skill_path}/update_config.py --show
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
2. Update a specific value using dot-notation for the key path:
|
|
27
|
+
```bash
|
|
28
|
+
python {skill_path}/update_config.py --set "llm.deepseek.apiKey" "sk-xxx"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
```bash
|
|
33
|
+
# Change LLM provider
|
|
34
|
+
python {skill_path}/update_config.py --set "llm.provider" "claude"
|
|
35
|
+
|
|
36
|
+
# Set Tavily API key
|
|
37
|
+
python {skill_path}/update_config.py --set "tavily.apiKey" "tvly-xxx"
|
|
38
|
+
|
|
39
|
+
# Set Telegram token
|
|
40
|
+
python {skill_path}/update_config.py --set "channels.telegram.token" "123:ABC"
|
|
41
|
+
|
|
42
|
+
# Set GitHub token
|
|
43
|
+
python {skill_path}/update_config.py --set "skills.github.token" "ghp_xxx"
|
|
44
|
+
|
|
45
|
+
# Change web port
|
|
46
|
+
python {skill_path}/update_config.py --set "web.port" "8080"
|
|
47
|
+
|
|
48
|
+
# Set email credentials
|
|
49
|
+
python {skill_path}/update_config.py --set "skills.email.senderEmail" "me@gmail.com"
|
|
50
|
+
python {skill_path}/update_config.py --set "skills.email.senderPassword" "app-password"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
3. After updating, tell the user the change was saved and whether
|
|
54
|
+
a restart is needed to take effect.
|
|
55
|
+
|
|
56
|
+
### Security Notes
|
|
57
|
+
|
|
58
|
+
- When displaying config, API keys / passwords / tokens are masked
|
|
59
|
+
- Only `pythonclaw.json` is modified — no other files
|
|
60
|
+
|
|
61
|
+
## Resources
|
|
62
|
+
|
|
63
|
+
| File | Description |
|
|
64
|
+
|------|-------------|
|
|
65
|
+
| `update_config.py` | CLI tool to read and update pythonclaw.json |
|
|
Binary file
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Read and update pythonclaw.json configuration values."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
CONFIG_FILE = "pythonclaw.json"
|
|
11
|
+
|
|
12
|
+
SENSITIVE_PATTERNS = re.compile(
|
|
13
|
+
r"(apikey|api_key|token|password|secret)", re.IGNORECASE
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_config() -> dict:
|
|
18
|
+
if not os.path.exists(CONFIG_FILE):
|
|
19
|
+
print(f"Error: {CONFIG_FILE} not found in {os.getcwd()}", file=sys.stderr)
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
22
|
+
return json.load(f)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _save_config(cfg: dict) -> None:
|
|
26
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
27
|
+
json.dump(cfg, f, indent=2, ensure_ascii=False)
|
|
28
|
+
f.write("\n")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _mask_value(key: str, value) -> str:
|
|
32
|
+
if isinstance(value, str) and SENSITIVE_PATTERNS.search(key) and value:
|
|
33
|
+
if len(value) <= 8:
|
|
34
|
+
return "****"
|
|
35
|
+
return value[:4] + "****" + value[-4:]
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _mask_dict(d: dict, parent_key: str = "") -> dict:
|
|
40
|
+
masked = {}
|
|
41
|
+
for k, v in d.items():
|
|
42
|
+
full_key = f"{parent_key}.{k}" if parent_key else k
|
|
43
|
+
if isinstance(v, dict):
|
|
44
|
+
masked[k] = _mask_dict(v, full_key)
|
|
45
|
+
else:
|
|
46
|
+
masked[k] = _mask_value(full_key, v)
|
|
47
|
+
return masked
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _get_nested(d: dict, key_path: str):
|
|
51
|
+
keys = key_path.split(".")
|
|
52
|
+
current = d
|
|
53
|
+
for k in keys:
|
|
54
|
+
if not isinstance(current, dict) or k not in current:
|
|
55
|
+
return None
|
|
56
|
+
current = current[k]
|
|
57
|
+
return current
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _set_nested(d: dict, key_path: str, value) -> None:
|
|
61
|
+
keys = key_path.split(".")
|
|
62
|
+
current = d
|
|
63
|
+
for k in keys[:-1]:
|
|
64
|
+
if k not in current or not isinstance(current[k], dict):
|
|
65
|
+
current[k] = {}
|
|
66
|
+
current = current[k]
|
|
67
|
+
|
|
68
|
+
old_value = current.get(keys[-1])
|
|
69
|
+
if isinstance(old_value, int):
|
|
70
|
+
try:
|
|
71
|
+
value = int(value)
|
|
72
|
+
except (ValueError, TypeError):
|
|
73
|
+
pass
|
|
74
|
+
elif isinstance(old_value, bool) or (isinstance(value, str) and value.lower() in ("true", "false")):
|
|
75
|
+
value = value.lower() in ("true", "1", "yes") if isinstance(value, str) else value
|
|
76
|
+
elif isinstance(old_value, list) and isinstance(value, str):
|
|
77
|
+
try:
|
|
78
|
+
value = json.loads(value)
|
|
79
|
+
except json.JSONDecodeError:
|
|
80
|
+
value = [v.strip() for v in value.split(",") if v.strip()]
|
|
81
|
+
|
|
82
|
+
current[keys[-1]] = value
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def show_config():
|
|
86
|
+
cfg = _load_config()
|
|
87
|
+
masked = _mask_dict(cfg)
|
|
88
|
+
print(json.dumps(masked, indent=2, ensure_ascii=False))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_value(key_path: str):
|
|
92
|
+
cfg = _load_config()
|
|
93
|
+
val = _get_nested(cfg, key_path)
|
|
94
|
+
if val is None:
|
|
95
|
+
print(f"Key '{key_path}' not found")
|
|
96
|
+
sys.exit(1)
|
|
97
|
+
masked = _mask_value(key_path, val) if isinstance(val, str) else val
|
|
98
|
+
if isinstance(masked, dict):
|
|
99
|
+
masked = _mask_dict(masked, key_path)
|
|
100
|
+
print(json.dumps(masked, indent=2, ensure_ascii=False) if isinstance(masked, (dict, list)) else masked)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def set_value(key_path: str, value: str):
|
|
104
|
+
cfg = _load_config()
|
|
105
|
+
_set_nested(cfg, key_path, value)
|
|
106
|
+
_save_config(cfg)
|
|
107
|
+
display_val = _mask_value(key_path, value)
|
|
108
|
+
print(f"Updated {key_path} = {display_val}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def main():
|
|
112
|
+
parser = argparse.ArgumentParser(description="Manage pythonclaw.json")
|
|
113
|
+
parser.add_argument("--show", action="store_true", help="Show all config (masked)")
|
|
114
|
+
parser.add_argument("--get", metavar="KEY", help="Get a config value by dot-path")
|
|
115
|
+
parser.add_argument("--set", nargs=2, metavar=("KEY", "VALUE"), help="Set a config value")
|
|
116
|
+
args = parser.parse_args()
|
|
117
|
+
|
|
118
|
+
if args.show:
|
|
119
|
+
show_config()
|
|
120
|
+
elif args.get:
|
|
121
|
+
get_value(args.get)
|
|
122
|
+
elif args.set:
|
|
123
|
+
set_value(args.set[0], args.set[1])
|
|
124
|
+
else:
|
|
125
|
+
parser.print_help()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
main()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: change_soul
|
|
3
|
+
description: >
|
|
4
|
+
Modify the agent's core identity (soul.md). Use when the user wants to
|
|
5
|
+
change their name, the agent's core values, language preference, or
|
|
6
|
+
fundamental behavior.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Instructions
|
|
10
|
+
|
|
11
|
+
Modify the agent's soul (core identity) file at `context/soul/SOUL.md`.
|
|
12
|
+
|
|
13
|
+
### When to Use
|
|
14
|
+
|
|
15
|
+
- User says "change my name to ...", "call me ...", "change language to ..."
|
|
16
|
+
- User wants to modify core values or ethical boundaries
|
|
17
|
+
- User asks to update fundamental agent behavior
|
|
18
|
+
|
|
19
|
+
### How to Use
|
|
20
|
+
|
|
21
|
+
1. Ask the user what they want to change
|
|
22
|
+
2. Read the current soul file:
|
|
23
|
+
```
|
|
24
|
+
read_file("context/soul/SOUL.md")
|
|
25
|
+
```
|
|
26
|
+
3. Modify the relevant section and write it back:
|
|
27
|
+
```
|
|
28
|
+
write_file("context/soul/SOUL.md", "...updated content...")
|
|
29
|
+
```
|
|
30
|
+
4. Tell the user: "Soul updated. Use `/clear` to apply the changes in
|
|
31
|
+
a fresh conversation, or they will take effect on next restart."
|
|
32
|
+
|
|
33
|
+
### Important
|
|
34
|
+
|
|
35
|
+
- Preserve the overall structure of SOUL.md
|
|
36
|
+
- Only change the specific section the user asked about
|
|
37
|
+
- Keep core ethical boundaries intact — never remove safety guidelines
|
|
38
|
+
|
|
39
|
+
## Resources
|
|
40
|
+
|
|
41
|
+
This skill uses the built-in `read_file` and `write_file` tools directly.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: onboarding
|
|
3
|
+
description: >
|
|
4
|
+
First-time setup wizard. Asks the user their preferred name, desired
|
|
5
|
+
agent personality, and focus area, then writes soul.md and persona.md.
|
|
6
|
+
Use when the agent starts for the first time with no soul/persona
|
|
7
|
+
configured, or when the user asks to reconfigure their agent identity.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Instructions
|
|
11
|
+
|
|
12
|
+
Guide the user through initial agent setup with a friendly conversation.
|
|
13
|
+
|
|
14
|
+
### When to Use
|
|
15
|
+
|
|
16
|
+
- Agent starts with empty/default soul.md and persona.md
|
|
17
|
+
- User says "reconfigure", "setup", "change my agent", etc.
|
|
18
|
+
|
|
19
|
+
### Onboarding Flow
|
|
20
|
+
|
|
21
|
+
Ask these questions **one at a time** in a friendly, conversational tone:
|
|
22
|
+
|
|
23
|
+
1. **Name**: "What should I call you?"
|
|
24
|
+
2. **Personality**: "What kind of personality would you like me to have?
|
|
25
|
+
For example: professional & concise, friendly & casual, humorous,
|
|
26
|
+
formal, encouraging, etc."
|
|
27
|
+
3. **Focus area**: "What area would you like me to focus on?
|
|
28
|
+
For example: software development, finance & investing, research,
|
|
29
|
+
daily assistant, creative writing, etc."
|
|
30
|
+
4. **Language preference**: "What language do you prefer I respond in?
|
|
31
|
+
(English, Chinese, etc.)"
|
|
32
|
+
|
|
33
|
+
### After Collecting Answers
|
|
34
|
+
|
|
35
|
+
Use `run_command` to write the files:
|
|
36
|
+
|
|
37
|
+
**Write soul.md:**
|
|
38
|
+
```bash
|
|
39
|
+
python {skill_path}/write_identity.py --type soul \
|
|
40
|
+
--user-name "NAME" \
|
|
41
|
+
--personality "PERSONALITY" \
|
|
42
|
+
--focus "FOCUS" \
|
|
43
|
+
--language "LANGUAGE"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Write persona.md:**
|
|
47
|
+
```bash
|
|
48
|
+
python {skill_path}/write_identity.py --type persona \
|
|
49
|
+
--user-name "NAME" \
|
|
50
|
+
--personality "PERSONALITY" \
|
|
51
|
+
--focus "FOCUS" \
|
|
52
|
+
--language "LANGUAGE"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
After writing, tell the user: "Setup complete! Your preferences have been
|
|
56
|
+
saved. Use `/clear` to start a fresh conversation with your new identity,
|
|
57
|
+
or just keep chatting."
|
|
58
|
+
|
|
59
|
+
## Resources
|
|
60
|
+
|
|
61
|
+
| File | Description |
|
|
62
|
+
|------|-------------|
|
|
63
|
+
| `write_identity.py` | Generates soul.md and persona.md files |
|
|
Binary file
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate soul.md or persona.md from user preferences."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
SOUL_TEMPLATE = """# PythonClaw — Soul
|
|
8
|
+
|
|
9
|
+
You are a PythonClaw agent — an autonomous AI assistant.
|
|
10
|
+
|
|
11
|
+
This document defines your core identity — the values, principles, and character
|
|
12
|
+
that remain constant regardless of which persona or role you are playing.
|
|
13
|
+
|
|
14
|
+
## User
|
|
15
|
+
|
|
16
|
+
- The user's name is **{user_name}**.
|
|
17
|
+
- Always address them by name when appropriate.
|
|
18
|
+
- Preferred language: **{language}**. Always respond in this language unless
|
|
19
|
+
the user explicitly switches.
|
|
20
|
+
|
|
21
|
+
## Core Values
|
|
22
|
+
|
|
23
|
+
- **Honesty**: You never fabricate facts. When uncertain, say so clearly.
|
|
24
|
+
- **Helpfulness**: Your primary purpose is to genuinely help {user_name}.
|
|
25
|
+
Look for the real need behind a request.
|
|
26
|
+
- **Respect**: Treat {user_name} with dignity. Adapt your communication
|
|
27
|
+
style to their preferences.
|
|
28
|
+
- **Curiosity**: You are genuinely interested in problems. Ask clarifying
|
|
29
|
+
questions when needed.
|
|
30
|
+
- **Responsibility**: Think before you act. Consider side-effects and
|
|
31
|
+
prefer reversible actions.
|
|
32
|
+
|
|
33
|
+
## Ethical Boundaries
|
|
34
|
+
|
|
35
|
+
- Never help with anything that could cause serious harm.
|
|
36
|
+
- Never deceive or manipulate.
|
|
37
|
+
- If asked to do something unethical, explain why and offer alternatives.
|
|
38
|
+
|
|
39
|
+
## Emotional Character
|
|
40
|
+
|
|
41
|
+
{personality_description}
|
|
42
|
+
|
|
43
|
+
## Relationship with {user_name}
|
|
44
|
+
|
|
45
|
+
Remember that {user_name} has goals and context beyond this conversation.
|
|
46
|
+
Treat their time as valuable. Keep responses concise, expanding only when
|
|
47
|
+
depth is genuinely needed.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
*This soul file is loaded at agent startup.*
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
PERSONA_TEMPLATE = """# PythonClaw — Persona
|
|
54
|
+
|
|
55
|
+
## Role
|
|
56
|
+
|
|
57
|
+
You are {user_name}'s personal AI assistant, specializing in **{focus}**.
|
|
58
|
+
|
|
59
|
+
## Personality
|
|
60
|
+
|
|
61
|
+
{personality_traits}
|
|
62
|
+
|
|
63
|
+
## Focus Area
|
|
64
|
+
|
|
65
|
+
Your primary expertise and focus is: **{focus}**.
|
|
66
|
+
|
|
67
|
+
When {user_name} asks questions in this area, provide deep, detailed,
|
|
68
|
+
expert-level answers. For topics outside your focus, still help but
|
|
69
|
+
mention when something is outside your specialty.
|
|
70
|
+
|
|
71
|
+
## Communication Style
|
|
72
|
+
|
|
73
|
+
- Respond in **{language}**
|
|
74
|
+
- {style_notes}
|
|
75
|
+
- Use examples and analogies when explaining complex topics
|
|
76
|
+
- Be proactive — suggest relevant follow-ups and related insights
|
|
77
|
+
|
|
78
|
+
## Specialization Guidelines
|
|
79
|
+
|
|
80
|
+
{focus_guidelines}
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _personality_description(personality: str) -> str:
|
|
85
|
+
p = personality.lower()
|
|
86
|
+
if any(w in p for w in ("professional", "formal", "concise")):
|
|
87
|
+
return (
|
|
88
|
+
"You are professional, measured, and precise. You get straight "
|
|
89
|
+
"to the point without unnecessary preamble. Your tone is "
|
|
90
|
+
"respectful and business-like."
|
|
91
|
+
)
|
|
92
|
+
if any(w in p for w in ("friendly", "casual", "warm")):
|
|
93
|
+
return (
|
|
94
|
+
"You are warm, approachable, and conversational. You use a "
|
|
95
|
+
"natural, relaxed tone while remaining helpful and accurate. "
|
|
96
|
+
"You occasionally use light humor when appropriate."
|
|
97
|
+
)
|
|
98
|
+
if any(w in p for w in ("humor", "funny", "witty")):
|
|
99
|
+
return (
|
|
100
|
+
"You have a sharp wit and enjoy making conversations engaging "
|
|
101
|
+
"with well-timed humor. You balance entertainment with "
|
|
102
|
+
"genuinely useful information."
|
|
103
|
+
)
|
|
104
|
+
if any(w in p for w in ("encouraging", "supportive", "coach")):
|
|
105
|
+
return (
|
|
106
|
+
"You are encouraging and supportive, like a patient mentor. "
|
|
107
|
+
"You celebrate progress, provide constructive feedback, and "
|
|
108
|
+
"help build confidence."
|
|
109
|
+
)
|
|
110
|
+
return (
|
|
111
|
+
f"Your personality is: {personality}. "
|
|
112
|
+
"You embody these traits consistently in every interaction."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _personality_traits(personality: str) -> str:
|
|
117
|
+
return f"- Core trait: **{personality}**\n- Consistent across all interactions"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _style_notes(personality: str) -> str:
|
|
121
|
+
p = personality.lower()
|
|
122
|
+
if "concise" in p or "professional" in p:
|
|
123
|
+
return "Keep answers brief and structured with bullet points"
|
|
124
|
+
if "casual" in p or "friendly" in p:
|
|
125
|
+
return "Use a conversational, natural tone"
|
|
126
|
+
if "humor" in p or "funny" in p:
|
|
127
|
+
return "Include wit and humor while staying informative"
|
|
128
|
+
return "Adapt your tone to the context of each conversation"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _focus_guidelines(focus: str) -> str:
|
|
132
|
+
f = focus.lower()
|
|
133
|
+
if any(w in f for w in ("software", "dev", "coding", "programming")):
|
|
134
|
+
return (
|
|
135
|
+
"- Provide clean, production-quality code examples\n"
|
|
136
|
+
"- Explain architectural decisions and trade-offs\n"
|
|
137
|
+
"- Follow best practices and current industry standards\n"
|
|
138
|
+
"- Consider security, performance, and maintainability"
|
|
139
|
+
)
|
|
140
|
+
if any(w in f for w in ("finance", "invest", "stock", "trading")):
|
|
141
|
+
return (
|
|
142
|
+
"- Always include disclaimers that you are not a financial advisor\n"
|
|
143
|
+
"- Use real data from web searches when available\n"
|
|
144
|
+
"- Consider risk factors and diversification\n"
|
|
145
|
+
"- Present bull and bear cases for balanced analysis"
|
|
146
|
+
)
|
|
147
|
+
if any(w in f for w in ("research", "academic", "science")):
|
|
148
|
+
return (
|
|
149
|
+
"- Cite sources and provide references when possible\n"
|
|
150
|
+
"- Distinguish between established facts and hypotheses\n"
|
|
151
|
+
"- Use precise, academic language when appropriate\n"
|
|
152
|
+
"- Encourage critical thinking and methodology"
|
|
153
|
+
)
|
|
154
|
+
if any(w in f for w in ("daily", "assistant", "productivity")):
|
|
155
|
+
return (
|
|
156
|
+
"- Be proactive with reminders and suggestions\n"
|
|
157
|
+
"- Help organize tasks and priorities\n"
|
|
158
|
+
"- Provide practical, actionable advice\n"
|
|
159
|
+
"- Learn user patterns and preferences over time"
|
|
160
|
+
)
|
|
161
|
+
if any(w in f for w in ("creative", "writing", "content")):
|
|
162
|
+
return (
|
|
163
|
+
"- Offer diverse creative perspectives and styles\n"
|
|
164
|
+
"- Provide constructive feedback on creative work\n"
|
|
165
|
+
"- Help brainstorm and develop ideas\n"
|
|
166
|
+
"- Balance originality with user's vision"
|
|
167
|
+
)
|
|
168
|
+
return f"- Focus deeply on: {focus}\n- Provide expert-level guidance in this area"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def write_soul(user_name: str, personality: str, focus: str, language: str) -> str:
|
|
172
|
+
content = SOUL_TEMPLATE.format(
|
|
173
|
+
user_name=user_name,
|
|
174
|
+
language=language,
|
|
175
|
+
personality_description=_personality_description(personality),
|
|
176
|
+
)
|
|
177
|
+
path = os.path.join("context", "soul", "SOUL.md")
|
|
178
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
179
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
180
|
+
f.write(content.strip() + "\n")
|
|
181
|
+
return path
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def write_persona(user_name: str, personality: str, focus: str, language: str) -> str:
|
|
185
|
+
content = PERSONA_TEMPLATE.format(
|
|
186
|
+
user_name=user_name,
|
|
187
|
+
focus=focus,
|
|
188
|
+
language=language,
|
|
189
|
+
personality_traits=_personality_traits(personality),
|
|
190
|
+
style_notes=_style_notes(personality),
|
|
191
|
+
focus_guidelines=_focus_guidelines(focus),
|
|
192
|
+
)
|
|
193
|
+
path = os.path.join("context", "persona", "persona.md")
|
|
194
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
195
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
196
|
+
f.write(content.strip() + "\n")
|
|
197
|
+
return path
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def main():
|
|
201
|
+
parser = argparse.ArgumentParser(description="Generate soul.md or persona.md")
|
|
202
|
+
parser.add_argument("--type", required=True, choices=["soul", "persona"])
|
|
203
|
+
parser.add_argument("--user-name", required=True)
|
|
204
|
+
parser.add_argument("--personality", required=True)
|
|
205
|
+
parser.add_argument("--focus", required=True)
|
|
206
|
+
parser.add_argument("--language", default="English")
|
|
207
|
+
args = parser.parse_args()
|
|
208
|
+
|
|
209
|
+
if args.type == "soul":
|
|
210
|
+
path = write_soul(args.user_name, args.personality, args.focus, args.language)
|
|
211
|
+
else:
|
|
212
|
+
path = write_persona(args.user_name, args.personality, args.focus, args.language)
|
|
213
|
+
|
|
214
|
+
print(f"Written: {path}")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == "__main__":
|
|
218
|
+
main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: system_random
|
|
3
|
+
description: >
|
|
4
|
+
Generate random numbers, UUIDs, passwords, or pick random items from a list.
|
|
5
|
+
Use when the user needs any kind of randomness.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Instructions
|
|
9
|
+
|
|
10
|
+
Generate random values using the bundled script.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Random integer in range
|
|
14
|
+
python {skill_path}/random_util.py --int 1 100
|
|
15
|
+
|
|
16
|
+
# Random float in range
|
|
17
|
+
python {skill_path}/random_util.py --float 0.0 1.0
|
|
18
|
+
|
|
19
|
+
# UUID
|
|
20
|
+
python {skill_path}/random_util.py --uuid
|
|
21
|
+
|
|
22
|
+
# Random password (default 16 chars)
|
|
23
|
+
python {skill_path}/random_util.py --password 20
|
|
24
|
+
|
|
25
|
+
# Pick N random items from a comma-separated list
|
|
26
|
+
python {skill_path}/random_util.py --choice "apple,banana,cherry,date" --count 2
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Resources
|
|
30
|
+
|
|
31
|
+
| File | Description |
|
|
32
|
+
|------|-------------|
|
|
33
|
+
| `random_util.py` | CLI tool for generating random values |
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate random numbers, UUIDs, passwords, or pick random items."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import random
|
|
6
|
+
import string
|
|
7
|
+
import uuid
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
parser = argparse.ArgumentParser(description="Random value generator")
|
|
12
|
+
parser.add_argument("--int", nargs=2, type=int, metavar=("MIN", "MAX"),
|
|
13
|
+
help="Random integer in [MIN, MAX]")
|
|
14
|
+
parser.add_argument("--float", nargs=2, type=float, metavar=("MIN", "MAX"),
|
|
15
|
+
help="Random float in [MIN, MAX]")
|
|
16
|
+
parser.add_argument("--uuid", action="store_true", help="Generate a UUID4")
|
|
17
|
+
parser.add_argument("--password", type=int, metavar="LENGTH",
|
|
18
|
+
help="Generate a random password of given length")
|
|
19
|
+
parser.add_argument("--choice", type=str,
|
|
20
|
+
help="Comma-separated list to pick from")
|
|
21
|
+
parser.add_argument("--count", type=int, default=1,
|
|
22
|
+
help="Number of items to pick (for --choice)")
|
|
23
|
+
args = parser.parse_args()
|
|
24
|
+
|
|
25
|
+
if args.int:
|
|
26
|
+
print(random.randint(args.int[0], args.int[1]))
|
|
27
|
+
elif args.float:
|
|
28
|
+
print(round(random.uniform(args.float[0], args.float[1]), 6))
|
|
29
|
+
elif args.uuid:
|
|
30
|
+
print(uuid.uuid4())
|
|
31
|
+
elif args.password is not None:
|
|
32
|
+
length = max(args.password, 4)
|
|
33
|
+
chars = string.ascii_letters + string.digits + string.punctuation
|
|
34
|
+
print("".join(random.choices(chars, k=length)))
|
|
35
|
+
elif args.choice:
|
|
36
|
+
items = [x.strip() for x in args.choice.split(",") if x.strip()]
|
|
37
|
+
count = min(args.count, len(items))
|
|
38
|
+
picks = random.sample(items, count)
|
|
39
|
+
print(", ".join(picks))
|
|
40
|
+
else:
|
|
41
|
+
parser.print_help()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: system_time
|
|
3
|
+
description: >
|
|
4
|
+
Get the current date, time, timezone, or convert between timezones.
|
|
5
|
+
Use when the user asks what time it is, needs a date, or timezone conversion.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Instructions
|
|
9
|
+
|
|
10
|
+
Get time information using the bundled script.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Current local time
|
|
14
|
+
python {skill_path}/time_util.py
|
|
15
|
+
|
|
16
|
+
# Time in a specific timezone
|
|
17
|
+
python {skill_path}/time_util.py --tz "America/New_York"
|
|
18
|
+
|
|
19
|
+
# List common timezone names
|
|
20
|
+
python {skill_path}/time_util.py --list-tz
|
|
21
|
+
|
|
22
|
+
# Unix timestamp
|
|
23
|
+
python {skill_path}/time_util.py --unix
|
|
24
|
+
|
|
25
|
+
# Convert a time between timezones
|
|
26
|
+
python {skill_path}/time_util.py --convert "2026-03-01 14:30" --from-tz "Asia/Shanghai" --to-tz "America/New_York"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Resources
|
|
30
|
+
|
|
31
|
+
| File | Description |
|
|
32
|
+
|------|-------------|
|
|
33
|
+
| `time_util.py` | CLI tool for time queries and conversions |
|