pocketrb 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +32 -0
- data/LICENSE.txt +21 -0
- data/README.md +456 -0
- data/exe/pocketrb +6 -0
- data/lib/pocketrb/agent/compaction.rb +187 -0
- data/lib/pocketrb/agent/context.rb +171 -0
- data/lib/pocketrb/agent/loop.rb +276 -0
- data/lib/pocketrb/agent/spawn_tool.rb +72 -0
- data/lib/pocketrb/agent/subagent_manager.rb +196 -0
- data/lib/pocketrb/bus/events.rb +99 -0
- data/lib/pocketrb/bus/message_bus.rb +148 -0
- data/lib/pocketrb/channels/base.rb +69 -0
- data/lib/pocketrb/channels/cli.rb +109 -0
- data/lib/pocketrb/channels/telegram.rb +607 -0
- data/lib/pocketrb/channels/whatsapp.rb +242 -0
- data/lib/pocketrb/cli/base.rb +119 -0
- data/lib/pocketrb/cli/chat.rb +67 -0
- data/lib/pocketrb/cli/config.rb +52 -0
- data/lib/pocketrb/cli/cron.rb +144 -0
- data/lib/pocketrb/cli/gateway.rb +132 -0
- data/lib/pocketrb/cli/init.rb +39 -0
- data/lib/pocketrb/cli/plans.rb +28 -0
- data/lib/pocketrb/cli/skills.rb +34 -0
- data/lib/pocketrb/cli/start.rb +55 -0
- data/lib/pocketrb/cli/telegram.rb +93 -0
- data/lib/pocketrb/cli/version.rb +18 -0
- data/lib/pocketrb/cli/whatsapp.rb +60 -0
- data/lib/pocketrb/cli.rb +124 -0
- data/lib/pocketrb/config.rb +190 -0
- data/lib/pocketrb/cron/job.rb +155 -0
- data/lib/pocketrb/cron/service.rb +395 -0
- data/lib/pocketrb/heartbeat/service.rb +175 -0
- data/lib/pocketrb/mcp/client.rb +172 -0
- data/lib/pocketrb/mcp/memory_tool.rb +133 -0
- data/lib/pocketrb/media/processor.rb +258 -0
- data/lib/pocketrb/memory.rb +283 -0
- data/lib/pocketrb/planning/manager.rb +159 -0
- data/lib/pocketrb/planning/plan.rb +223 -0
- data/lib/pocketrb/planning/tool.rb +176 -0
- data/lib/pocketrb/providers/anthropic.rb +333 -0
- data/lib/pocketrb/providers/base.rb +98 -0
- data/lib/pocketrb/providers/claude_cli.rb +412 -0
- data/lib/pocketrb/providers/claude_max_proxy.rb +347 -0
- data/lib/pocketrb/providers/openrouter.rb +205 -0
- data/lib/pocketrb/providers/registry.rb +59 -0
- data/lib/pocketrb/providers/ruby_llm_provider.rb +136 -0
- data/lib/pocketrb/providers/types.rb +111 -0
- data/lib/pocketrb/session/manager.rb +192 -0
- data/lib/pocketrb/session/session.rb +204 -0
- data/lib/pocketrb/skills/builtin/github/SKILL.md +113 -0
- data/lib/pocketrb/skills/builtin/proactive/SKILL.md +101 -0
- data/lib/pocketrb/skills/builtin/reflection/SKILL.md +109 -0
- data/lib/pocketrb/skills/builtin/tmux/SKILL.md +130 -0
- data/lib/pocketrb/skills/builtin/weather/SKILL.md +130 -0
- data/lib/pocketrb/skills/create_tool.rb +115 -0
- data/lib/pocketrb/skills/loader.rb +164 -0
- data/lib/pocketrb/skills/modify_tool.rb +123 -0
- data/lib/pocketrb/skills/skill.rb +75 -0
- data/lib/pocketrb/tools/background_job_manager.rb +261 -0
- data/lib/pocketrb/tools/base.rb +118 -0
- data/lib/pocketrb/tools/browser.rb +152 -0
- data/lib/pocketrb/tools/browser_advanced.rb +470 -0
- data/lib/pocketrb/tools/browser_session.rb +167 -0
- data/lib/pocketrb/tools/cron.rb +222 -0
- data/lib/pocketrb/tools/edit_file.rb +101 -0
- data/lib/pocketrb/tools/exec.rb +194 -0
- data/lib/pocketrb/tools/jobs.rb +127 -0
- data/lib/pocketrb/tools/list_dir.rb +102 -0
- data/lib/pocketrb/tools/memory.rb +167 -0
- data/lib/pocketrb/tools/message.rb +70 -0
- data/lib/pocketrb/tools/para_memory.rb +264 -0
- data/lib/pocketrb/tools/read_file.rb +65 -0
- data/lib/pocketrb/tools/registry.rb +160 -0
- data/lib/pocketrb/tools/send_file.rb +158 -0
- data/lib/pocketrb/tools/think.rb +35 -0
- data/lib/pocketrb/tools/web_fetch.rb +150 -0
- data/lib/pocketrb/tools/web_search.rb +102 -0
- data/lib/pocketrb/tools/write_file.rb +55 -0
- data/lib/pocketrb/version.rb +5 -0
- data/lib/pocketrb.rb +75 -0
- data/pocketrb.gemspec +60 -0
- metadata +327 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pocketrb
|
|
4
|
+
module Tools
|
|
5
|
+
# Base class for all tools
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :context
|
|
8
|
+
|
|
9
|
+
def initialize(context = {})
|
|
10
|
+
@context = context
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Tool name (must be unique)
|
|
14
|
+
# @return [String]
|
|
15
|
+
def name
|
|
16
|
+
raise NotImplementedError, "#{self.class}#name must be implemented"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Human-readable description
|
|
20
|
+
# @return [String]
|
|
21
|
+
def description
|
|
22
|
+
raise NotImplementedError, "#{self.class}#description must be implemented"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# JSON Schema for parameters
|
|
26
|
+
# @return [Hash]
|
|
27
|
+
def parameters
|
|
28
|
+
{ type: "object", properties: {}, required: [] }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Execute the tool
|
|
32
|
+
# @param kwargs [Hash] Tool arguments
|
|
33
|
+
# @return [String] Result
|
|
34
|
+
def execute(**kwargs)
|
|
35
|
+
raise NotImplementedError, "#{self.class}#execute must be implemented"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Check if tool is available in current context
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def available?
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Convert to OpenAI/Anthropic tool definition format
|
|
45
|
+
# @return [Hash]
|
|
46
|
+
def to_definition
|
|
47
|
+
{
|
|
48
|
+
type: "function",
|
|
49
|
+
function: {
|
|
50
|
+
name: name,
|
|
51
|
+
description: description,
|
|
52
|
+
parameters: parameters
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Convert to Anthropic-native tool format
|
|
58
|
+
# @return [Hash]
|
|
59
|
+
def to_anthropic_definition
|
|
60
|
+
{
|
|
61
|
+
name: name,
|
|
62
|
+
description: description,
|
|
63
|
+
input_schema: parameters
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
protected
|
|
68
|
+
|
|
69
|
+
# Helper to build a successful result
|
|
70
|
+
def success(message)
|
|
71
|
+
message.to_s
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Helper to build an error result
|
|
75
|
+
def error(message)
|
|
76
|
+
"Error: #{message}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Access workspace from context
|
|
80
|
+
def workspace
|
|
81
|
+
@context[:workspace]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Access bus from context
|
|
85
|
+
def bus
|
|
86
|
+
@context[:bus]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Resolve a path relative to workspace
|
|
90
|
+
def resolve_path(path)
|
|
91
|
+
return Pathname.new(path) if Pathname.new(path).absolute?
|
|
92
|
+
|
|
93
|
+
workspace ? workspace.join(path) : Pathname.new(path)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Check if path is within workspace (security)
|
|
97
|
+
def path_allowed?(path)
|
|
98
|
+
return true unless workspace
|
|
99
|
+
|
|
100
|
+
resolved = resolve_path(path).expand_path
|
|
101
|
+
workspace_expanded = workspace.expand_path
|
|
102
|
+
|
|
103
|
+
resolved.to_s.start_with?(workspace_expanded.to_s)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Validate path is allowed and exists
|
|
107
|
+
def validate_path!(path, must_exist: true)
|
|
108
|
+
resolved = resolve_path(path)
|
|
109
|
+
|
|
110
|
+
raise ToolError, "Path #{path} is outside workspace" unless path_allowed?(path)
|
|
111
|
+
|
|
112
|
+
raise ToolError, "Path does not exist: #{path}" if must_exist && !resolved.exist?
|
|
113
|
+
|
|
114
|
+
resolved
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "playwright"
|
|
4
|
+
|
|
5
|
+
module Pocketrb
|
|
6
|
+
module Tools
|
|
7
|
+
# Browser automation tool using Playwright
|
|
8
|
+
class Browser < Base
|
|
9
|
+
def name
|
|
10
|
+
"browser"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def description
|
|
14
|
+
"Automate browser interactions using headless Chromium. Can navigate to URLs, extract text content, take screenshots, interact with elements, and execute JavaScript. Use this for web scraping, research, testing, or any task requiring web browsing."
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parameters
|
|
18
|
+
{
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
action: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Action to perform",
|
|
24
|
+
enum: %w[navigate extract_text screenshot click type execute_js]
|
|
25
|
+
},
|
|
26
|
+
url: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "URL to navigate to (required for navigate action)"
|
|
29
|
+
},
|
|
30
|
+
selector: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "CSS selector for element interaction (required for click, type actions)"
|
|
33
|
+
},
|
|
34
|
+
text: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Text to type (required for type action)"
|
|
37
|
+
},
|
|
38
|
+
javascript: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "JavaScript code to execute (required for execute_js action)"
|
|
41
|
+
},
|
|
42
|
+
screenshot_path: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Path to save screenshot (optional, defaults to workspace/screenshot.png)"
|
|
45
|
+
},
|
|
46
|
+
wait_time: {
|
|
47
|
+
type: "integer",
|
|
48
|
+
description: "Milliseconds to wait after navigation (default: 1000)"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
required: ["action"]
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def execute(action:, url: nil, selector: nil, text: nil, javascript: nil, screenshot_path: nil, wait_time: 1000,
|
|
56
|
+
**_kwargs)
|
|
57
|
+
case action
|
|
58
|
+
when "navigate"
|
|
59
|
+
return error("URL required for navigate action") unless url
|
|
60
|
+
|
|
61
|
+
navigate_and_extract(url, wait_time)
|
|
62
|
+
when "extract_text"
|
|
63
|
+
extract_text_from_page
|
|
64
|
+
when "screenshot"
|
|
65
|
+
take_screenshot(screenshot_path || "screenshot.png")
|
|
66
|
+
when "click"
|
|
67
|
+
return error("Selector required for click action") unless selector
|
|
68
|
+
|
|
69
|
+
click_element(selector, wait_time)
|
|
70
|
+
when "type"
|
|
71
|
+
return error("Selector and text required for type action") unless selector && text
|
|
72
|
+
|
|
73
|
+
type_into_element(selector, text, wait_time)
|
|
74
|
+
when "execute_js"
|
|
75
|
+
return error("JavaScript required for execute_js action") unless javascript
|
|
76
|
+
|
|
77
|
+
execute_javascript(javascript)
|
|
78
|
+
else
|
|
79
|
+
error("Unknown action: #{action}")
|
|
80
|
+
end
|
|
81
|
+
rescue Playwright::Error => e
|
|
82
|
+
error("Playwright error: #{e.message}")
|
|
83
|
+
rescue StandardError => e
|
|
84
|
+
error("Unexpected error: #{e.message}")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def with_browser
|
|
90
|
+
Playwright.create(playwright_cli_executable_path: "npx playwright") do |playwright|
|
|
91
|
+
playwright.chromium.launch(headless: true) do |browser|
|
|
92
|
+
page = browser.new_page
|
|
93
|
+
@current_page = page
|
|
94
|
+
yield page
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
ensure
|
|
98
|
+
@current_page = nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def navigate_and_extract(url, wait_time)
|
|
102
|
+
with_browser do |page|
|
|
103
|
+
page.goto(url)
|
|
104
|
+
page.wait_for_timeout(wait_time)
|
|
105
|
+
|
|
106
|
+
title = page.title
|
|
107
|
+
content = page.text_content("body")
|
|
108
|
+
|
|
109
|
+
success("Title: #{title}\n\nContent:\n#{content[0..2000]}#{"...(truncated)" if content.length > 2000}")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def extract_text_from_page
|
|
114
|
+
return error("No page is currently loaded. Use navigate action first.") unless @current_page
|
|
115
|
+
|
|
116
|
+
content = @current_page.text_content("body")
|
|
117
|
+
success(content[0..5000] + (content.length > 5000 ? "...(truncated)" : ""))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def take_screenshot(path)
|
|
121
|
+
with_browser do |page|
|
|
122
|
+
screenshot_path = resolve_path(path)
|
|
123
|
+
page.screenshot(path: screenshot_path.to_s)
|
|
124
|
+
success("Screenshot saved to #{screenshot_path}")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def click_element(selector, wait_time)
|
|
129
|
+
with_browser do |page|
|
|
130
|
+
page.click(selector)
|
|
131
|
+
page.wait_for_timeout(wait_time)
|
|
132
|
+
success("Clicked element: #{selector}")
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def type_into_element(selector, text, wait_time)
|
|
137
|
+
with_browser do |page|
|
|
138
|
+
page.fill(selector, text)
|
|
139
|
+
page.wait_for_timeout(wait_time)
|
|
140
|
+
success("Typed into element: #{selector}")
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def execute_javascript(javascript)
|
|
145
|
+
with_browser do |page|
|
|
146
|
+
result = page.evaluate(javascript)
|
|
147
|
+
success("JavaScript executed. Result: #{result.inspect}")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|