squidbot 0.1.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.
- squidbot/__init__.py +5 -0
- squidbot/agent.py +263 -0
- squidbot/channels.py +271 -0
- squidbot/character.py +83 -0
- squidbot/client.py +318 -0
- squidbot/config.py +148 -0
- squidbot/daemon.py +310 -0
- squidbot/lanes.py +41 -0
- squidbot/main.py +157 -0
- squidbot/memory_db.py +706 -0
- squidbot/playwright_check.py +233 -0
- squidbot/plugins/__init__.py +47 -0
- squidbot/plugins/base.py +96 -0
- squidbot/plugins/hooks.py +416 -0
- squidbot/plugins/loader.py +248 -0
- squidbot/plugins/web3_plugin.py +407 -0
- squidbot/scheduler.py +214 -0
- squidbot/server.py +487 -0
- squidbot/session.py +609 -0
- squidbot/skills.py +141 -0
- squidbot/skills_template/reminder/SKILL.md +13 -0
- squidbot/skills_template/search/SKILL.md +11 -0
- squidbot/skills_template/summarize/SKILL.md +14 -0
- squidbot/tools/__init__.py +100 -0
- squidbot/tools/base.py +42 -0
- squidbot/tools/browser.py +311 -0
- squidbot/tools/coding.py +599 -0
- squidbot/tools/cron.py +218 -0
- squidbot/tools/memory_tool.py +152 -0
- squidbot/tools/web_search.py +50 -0
- squidbot-0.1.0.dist-info/METADATA +542 -0
- squidbot-0.1.0.dist-info/RECORD +34 -0
- squidbot-0.1.0.dist-info/WHEEL +4 -0
- squidbot-0.1.0.dist-info/entry_points.txt +4 -0
squidbot/tools/cron.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Cron/scheduling tools for proactive messaging."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from ..config import CRON_FILE
|
|
8
|
+
from .base import Tool
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_cron_jobs() -> list[dict]:
|
|
12
|
+
"""Load cron jobs from file."""
|
|
13
|
+
if not CRON_FILE.exists():
|
|
14
|
+
return []
|
|
15
|
+
try:
|
|
16
|
+
with open(CRON_FILE, "r") as f:
|
|
17
|
+
return json.load(f)
|
|
18
|
+
except (json.JSONDecodeError, IOError):
|
|
19
|
+
return []
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def save_cron_jobs(jobs: list[dict]) -> None:
|
|
23
|
+
"""Save cron jobs to file."""
|
|
24
|
+
with open(CRON_FILE, "w") as f:
|
|
25
|
+
json.dump(jobs, f, indent=2, default=str)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CronCreateTool(Tool):
|
|
29
|
+
"""Create a scheduled reminder/task."""
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def name(self) -> str:
|
|
33
|
+
return "cron_create"
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def description(self) -> str:
|
|
37
|
+
return "Create a scheduled task. The agent will execute the task (using tools if needed) at the specified time and send the result to the user."
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def parameters(self) -> dict:
|
|
41
|
+
return {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"message": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Task/prompt for the agent to execute when triggered (e.g., 'Check TechCrunch for latest news and summarize')",
|
|
47
|
+
},
|
|
48
|
+
"delay_minutes": {
|
|
49
|
+
"type": "integer",
|
|
50
|
+
"description": "Minutes from now to trigger (for one-time reminders)",
|
|
51
|
+
},
|
|
52
|
+
"interval_seconds": {
|
|
53
|
+
"type": "integer",
|
|
54
|
+
"description": "Repeat every N seconds (for recurring interval tasks, e.g., 20 for every 20 seconds)",
|
|
55
|
+
},
|
|
56
|
+
"cron_expression": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "Cron expression for recurring tasks (e.g., '0 9 * * *' for daily at 9am)",
|
|
59
|
+
},
|
|
60
|
+
"recurring": {
|
|
61
|
+
"type": "boolean",
|
|
62
|
+
"description": "Whether this is a recurring task",
|
|
63
|
+
"default": False,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
"required": ["message"],
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async def execute(
|
|
70
|
+
self,
|
|
71
|
+
message: str,
|
|
72
|
+
delay_minutes: Optional[int] = None,
|
|
73
|
+
interval_seconds: Optional[int] = None,
|
|
74
|
+
cron_expression: Optional[str] = None,
|
|
75
|
+
recurring: bool = False,
|
|
76
|
+
) -> str:
|
|
77
|
+
jobs = load_cron_jobs()
|
|
78
|
+
|
|
79
|
+
job = {
|
|
80
|
+
"id": len(jobs) + 1,
|
|
81
|
+
"message": message,
|
|
82
|
+
"created_at": datetime.now().isoformat(),
|
|
83
|
+
"recurring": recurring,
|
|
84
|
+
"enabled": True,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if delay_minutes:
|
|
88
|
+
trigger_at = datetime.now() + timedelta(minutes=delay_minutes)
|
|
89
|
+
job["trigger_at"] = trigger_at.isoformat()
|
|
90
|
+
job["type"] = "one_time"
|
|
91
|
+
elif interval_seconds:
|
|
92
|
+
job["interval_seconds"] = interval_seconds
|
|
93
|
+
job["type"] = "interval"
|
|
94
|
+
job["recurring"] = True
|
|
95
|
+
# Set next trigger time
|
|
96
|
+
job["next_trigger"] = (
|
|
97
|
+
datetime.now() + timedelta(seconds=interval_seconds)
|
|
98
|
+
).isoformat()
|
|
99
|
+
elif cron_expression:
|
|
100
|
+
job["cron_expression"] = cron_expression
|
|
101
|
+
job["type"] = "cron"
|
|
102
|
+
else:
|
|
103
|
+
return "Error: Must specify delay_minutes, interval_seconds, or cron_expression"
|
|
104
|
+
|
|
105
|
+
jobs.append(job)
|
|
106
|
+
save_cron_jobs(jobs)
|
|
107
|
+
|
|
108
|
+
if delay_minutes:
|
|
109
|
+
return f"Reminder set for {delay_minutes} minutes from now (id={job['id']}): {message}"
|
|
110
|
+
elif interval_seconds:
|
|
111
|
+
return f"Interval task scheduled every {interval_seconds} seconds (id={job['id']}): {message}"
|
|
112
|
+
else:
|
|
113
|
+
return f"Recurring task scheduled with cron '{cron_expression}' (id={job['id']}): {message}"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class CronListTool(Tool):
|
|
117
|
+
"""List scheduled tasks."""
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def name(self) -> str:
|
|
121
|
+
return "cron_list"
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def description(self) -> str:
|
|
125
|
+
return "List all scheduled reminders and tasks."
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def parameters(self) -> dict:
|
|
129
|
+
return {"type": "object", "properties": {}, "required": []}
|
|
130
|
+
|
|
131
|
+
async def execute(self) -> str:
|
|
132
|
+
jobs = load_cron_jobs()
|
|
133
|
+
if not jobs:
|
|
134
|
+
return "No scheduled tasks."
|
|
135
|
+
|
|
136
|
+
lines = [f"Scheduled tasks ({len(jobs)}):"]
|
|
137
|
+
for job in jobs:
|
|
138
|
+
status = "enabled" if job.get("enabled", True) else "disabled"
|
|
139
|
+
job_type = job.get("type", "unknown")
|
|
140
|
+
|
|
141
|
+
if job_type == "one_time":
|
|
142
|
+
lines.append(
|
|
143
|
+
f"- [{job['id']}] {status} | At {job['trigger_at']}: {job['message']}"
|
|
144
|
+
)
|
|
145
|
+
elif job_type == "interval":
|
|
146
|
+
lines.append(
|
|
147
|
+
f"- [{job['id']}] {status} | Every {job['interval_seconds']}s: {job['message']}"
|
|
148
|
+
)
|
|
149
|
+
elif job_type == "cron":
|
|
150
|
+
lines.append(
|
|
151
|
+
f"- [{job['id']}] {status} | Cron {job['cron_expression']}: {job['message']}"
|
|
152
|
+
)
|
|
153
|
+
else:
|
|
154
|
+
lines.append(f"- [{job['id']}] {status} | {job['message']}")
|
|
155
|
+
|
|
156
|
+
return "\n".join(lines)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class CronDeleteTool(Tool):
|
|
160
|
+
"""Delete a scheduled task."""
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def name(self) -> str:
|
|
164
|
+
return "cron_delete"
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def description(self) -> str:
|
|
168
|
+
return "Delete a scheduled task by ID. Use cron_list first to see job IDs. Use cron_clear to delete ALL jobs."
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def parameters(self) -> dict:
|
|
172
|
+
return {
|
|
173
|
+
"type": "object",
|
|
174
|
+
"properties": {
|
|
175
|
+
"job_id": {"type": "integer", "description": "ID of the job to delete"}
|
|
176
|
+
},
|
|
177
|
+
"required": ["job_id"],
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async def execute(self, job_id: int) -> str:
|
|
181
|
+
# Ensure job_id is an integer (LLM might pass string)
|
|
182
|
+
job_id = int(job_id)
|
|
183
|
+
|
|
184
|
+
jobs = load_cron_jobs()
|
|
185
|
+
original_len = len(jobs)
|
|
186
|
+
jobs = [j for j in jobs if j["id"] != job_id]
|
|
187
|
+
|
|
188
|
+
if len(jobs) == original_len:
|
|
189
|
+
return f"No job found with id={job_id}"
|
|
190
|
+
|
|
191
|
+
save_cron_jobs(jobs)
|
|
192
|
+
return f"Deleted job id={job_id}"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class CronClearTool(Tool):
|
|
196
|
+
"""Clear all scheduled tasks."""
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def name(self) -> str:
|
|
200
|
+
return "cron_clear"
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def description(self) -> str:
|
|
204
|
+
return "Delete ALL scheduled tasks. Use this to clear all cron jobs at once."
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def parameters(self) -> dict:
|
|
208
|
+
return {"type": "object", "properties": {}, "required": []}
|
|
209
|
+
|
|
210
|
+
async def execute(self) -> str:
|
|
211
|
+
jobs = load_cron_jobs()
|
|
212
|
+
count = len(jobs)
|
|
213
|
+
|
|
214
|
+
if count == 0:
|
|
215
|
+
return "No scheduled tasks to clear."
|
|
216
|
+
|
|
217
|
+
save_cron_jobs([])
|
|
218
|
+
return f"Cleared all {count} scheduled task(s)."
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Memory tools using SQLite with vector search."""
|
|
2
|
+
|
|
3
|
+
from ..memory_db import (add_memory, delete_memory, load_all_memories,
|
|
4
|
+
search_memory, search_memory_semantic)
|
|
5
|
+
from .base import Tool
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MemoryAddTool(Tool):
|
|
9
|
+
"""Add information to persistent memory."""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def name(self) -> str:
|
|
13
|
+
return "memory_add"
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def description(self) -> str:
|
|
17
|
+
return "Store information in persistent memory with semantic search support. Use this to remember facts, preferences, or important details."
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def parameters(self) -> dict:
|
|
21
|
+
return {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"properties": {
|
|
24
|
+
"content": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "The information to remember",
|
|
27
|
+
},
|
|
28
|
+
"category": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Optional category (e.g., 'preference', 'fact', 'task')",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
"required": ["content"],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async def execute(self, content: str, category: str = None) -> str:
|
|
37
|
+
entry = await add_memory(content, category)
|
|
38
|
+
return f"Stored in memory (id={entry['id']}): {content}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MemorySearchTool(Tool):
|
|
42
|
+
"""Search persistent memory using semantic similarity."""
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def name(self) -> str:
|
|
46
|
+
return "memory_search"
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def description(self) -> str:
|
|
50
|
+
return "Search persistent memory using semantic similarity. Finds related memories even if exact words don't match."
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def parameters(self) -> dict:
|
|
54
|
+
return {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": {
|
|
57
|
+
"query": {"type": "string", "description": "Search query"},
|
|
58
|
+
"semantic": {
|
|
59
|
+
"type": "boolean",
|
|
60
|
+
"description": "Use semantic search (default true)",
|
|
61
|
+
"default": True,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"required": ["query"],
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async def execute(self, query: str, semantic: bool = True) -> str:
|
|
68
|
+
if semantic:
|
|
69
|
+
results = await search_memory_semantic(query)
|
|
70
|
+
else:
|
|
71
|
+
results = await search_memory(query)
|
|
72
|
+
|
|
73
|
+
if not results:
|
|
74
|
+
return f"No memory entries found for: {query}"
|
|
75
|
+
|
|
76
|
+
lines = [f"Found {len(results)} memories:"]
|
|
77
|
+
for r in results:
|
|
78
|
+
cat = f"[{r['category']}] " if r.get("category") else ""
|
|
79
|
+
sim = f" (similarity: {r['similarity']:.2f})" if "similarity" in r else ""
|
|
80
|
+
lines.append(f"- {cat}{r['content']}{sim}")
|
|
81
|
+
|
|
82
|
+
return "\n".join(lines)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class MemoryListTool(Tool):
|
|
86
|
+
"""List all memories."""
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def name(self) -> str:
|
|
90
|
+
return "memory_list"
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def description(self) -> str:
|
|
94
|
+
return "List all stored memories."
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def parameters(self) -> dict:
|
|
98
|
+
return {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"properties": {
|
|
101
|
+
"limit": {
|
|
102
|
+
"type": "integer",
|
|
103
|
+
"description": "Maximum number of memories to return",
|
|
104
|
+
"default": 20,
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"required": [],
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async def execute(self, limit: int = 20) -> str:
|
|
111
|
+
entries = await load_all_memories(limit)
|
|
112
|
+
|
|
113
|
+
if not entries:
|
|
114
|
+
return "No memories stored yet."
|
|
115
|
+
|
|
116
|
+
lines = [f"Total memories: {len(entries)}"]
|
|
117
|
+
for e in entries:
|
|
118
|
+
cat = f"[{e['category']}] " if e.get("category") else ""
|
|
119
|
+
lines.append(f"- [{e['id']}] {cat}{e['content']}")
|
|
120
|
+
|
|
121
|
+
return "\n".join(lines)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class MemoryDeleteTool(Tool):
|
|
125
|
+
"""Delete a memory by ID."""
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def name(self) -> str:
|
|
129
|
+
return "memory_delete"
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def description(self) -> str:
|
|
133
|
+
return "Delete a specific memory by its ID."
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def parameters(self) -> dict:
|
|
137
|
+
return {
|
|
138
|
+
"type": "object",
|
|
139
|
+
"properties": {
|
|
140
|
+
"memory_id": {
|
|
141
|
+
"type": "integer",
|
|
142
|
+
"description": "ID of the memory to delete",
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"required": ["memory_id"],
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async def execute(self, memory_id: int) -> str:
|
|
149
|
+
success = await delete_memory(memory_id)
|
|
150
|
+
if success:
|
|
151
|
+
return f"Deleted memory id={memory_id}"
|
|
152
|
+
return f"No memory found with id={memory_id}"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Web search tool using DuckDuckGo."""
|
|
2
|
+
|
|
3
|
+
from duckduckgo_search import DDGS
|
|
4
|
+
|
|
5
|
+
from .base import Tool
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WebSearchTool(Tool):
|
|
9
|
+
"""Search the web using DuckDuckGo."""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def name(self) -> str:
|
|
13
|
+
return "web_search"
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def description(self) -> str:
|
|
17
|
+
return "Search the web for general information using DuckDuckGo. Use this for general queries. For visiting a SPECIFIC website (like techcrunch.com), use browser_navigate + browser_get_text instead."
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def parameters(self) -> dict:
|
|
21
|
+
return {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"properties": {
|
|
24
|
+
"query": {"type": "string", "description": "Search query"},
|
|
25
|
+
"max_results": {
|
|
26
|
+
"type": "integer",
|
|
27
|
+
"description": "Maximum number of results (default 5)",
|
|
28
|
+
"default": 5,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
"required": ["query"],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async def execute(self, query: str, max_results: int = 5) -> str:
|
|
35
|
+
try:
|
|
36
|
+
with DDGS() as ddgs:
|
|
37
|
+
results = list(ddgs.text(query, max_results=max_results))
|
|
38
|
+
|
|
39
|
+
if not results:
|
|
40
|
+
return f"No results found for: {query}"
|
|
41
|
+
|
|
42
|
+
lines = [f"Search results for: {query}\n"]
|
|
43
|
+
for i, r in enumerate(results, 1):
|
|
44
|
+
lines.append(f"{i}. {r['title']}")
|
|
45
|
+
lines.append(f" URL: {r['href']}")
|
|
46
|
+
lines.append(f" {r['body']}\n")
|
|
47
|
+
|
|
48
|
+
return "\n".join(lines)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
return f"Search error: {str(e)}"
|