pocketrb 0.1.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +37 -0
- data/README.md +98 -17
- data/lib/pocketrb/agent/compaction.rb +235 -22
- data/lib/pocketrb/agent/context.rb +26 -3
- data/lib/pocketrb/agent/loop.rb +53 -5
- data/lib/pocketrb/agent/spawn_tool.rb +14 -0
- data/lib/pocketrb/agent/subagent_manager.rb +24 -2
- data/lib/pocketrb/bus/events.rb +42 -1
- data/lib/pocketrb/bus/message_bus.rb +37 -0
- data/lib/pocketrb/channels/base.rb +13 -0
- data/lib/pocketrb/channels/cli.rb +16 -1
- data/lib/pocketrb/channels/telegram.rb +70 -4
- data/lib/pocketrb/channels/whatsapp.rb +49 -7
- data/lib/pocketrb/cli/chat.rb +7 -0
- data/lib/pocketrb/cli/config.rb +4 -0
- data/lib/pocketrb/cli/cron.rb +6 -0
- data/lib/pocketrb/cli.rb +0 -1
- data/lib/pocketrb/config.rb +55 -6
- data/lib/pocketrb/cron/job.rb +41 -0
- data/lib/pocketrb/cron/service.rb +44 -15
- data/lib/pocketrb/heartbeat/service.rb +21 -0
- data/lib/pocketrb/mcp/client.rb +41 -3
- data/lib/pocketrb/mcp/memory_tool.rb +33 -0
- data/lib/pocketrb/media/processor.rb +32 -1
- data/lib/pocketrb/memory.rb +12 -3
- data/lib/pocketrb/planning/manager.rb +29 -1
- data/lib/pocketrb/planning/plan.rb +52 -0
- data/lib/pocketrb/planning/tool.rb +45 -0
- data/lib/pocketrb/providers/anthropic.rb +113 -9
- data/lib/pocketrb/providers/base.rb +39 -4
- data/lib/pocketrb/providers/claude_cli.rb +79 -7
- data/lib/pocketrb/providers/claude_max_proxy.rb +72 -6
- data/lib/pocketrb/providers/openai_compatible.rb +79 -0
- data/lib/pocketrb/providers/openrouter.rb +72 -12
- data/lib/pocketrb/providers/registry.rb +24 -0
- data/lib/pocketrb/providers/ruby_llm_provider.rb +168 -54
- data/lib/pocketrb/providers/types.rb +51 -1
- data/lib/pocketrb/session/manager.rb +34 -2
- data/lib/pocketrb/session/session.rb +53 -11
- data/lib/pocketrb/skills/builtin/tmux/SKILL.md +9 -9
- data/lib/pocketrb/skills/create_tool.rb +28 -0
- data/lib/pocketrb/skills/loader.rb +22 -1
- data/lib/pocketrb/skills/modify_tool.rb +22 -0
- data/lib/pocketrb/skills/skill.rb +19 -3
- data/lib/pocketrb/tools/background_job_manager.rb +29 -0
- data/lib/pocketrb/tools/base.rb +16 -1
- data/lib/pocketrb/tools/browser.rb +40 -0
- data/lib/pocketrb/tools/browser_advanced.rb +94 -4
- data/lib/pocketrb/tools/browser_session.rb +18 -0
- data/lib/pocketrb/tools/cron.rb +46 -0
- data/lib/pocketrb/tools/edit_file.rb +16 -0
- data/lib/pocketrb/tools/exec.rb +46 -0
- data/lib/pocketrb/tools/jobs.rb +26 -2
- data/lib/pocketrb/tools/list_dir.rb +19 -0
- data/lib/pocketrb/tools/memory.rb +34 -0
- data/lib/pocketrb/tools/message.rb +15 -0
- data/lib/pocketrb/tools/para_memory.rb +47 -0
- data/lib/pocketrb/tools/read_file.rb +11 -0
- data/lib/pocketrb/tools/registry.rb +12 -4
- data/lib/pocketrb/tools/send_file.rb +29 -0
- data/lib/pocketrb/tools/think.rb +9 -0
- data/lib/pocketrb/tools/web_fetch.rb +58 -3
- data/lib/pocketrb/tools/web_search.rb +25 -0
- data/lib/pocketrb/tools/write_file.rb +10 -0
- data/lib/pocketrb/version.rb +2 -1
- data/lib/pocketrb.rb +4 -1
- data/pocketrb.gemspec +6 -8
- metadata +24 -44
|
@@ -6,14 +6,20 @@ module Pocketrb
|
|
|
6
6
|
module Tools
|
|
7
7
|
# Browser automation tool using Playwright
|
|
8
8
|
class Browser < Base
|
|
9
|
+
# Tool name
|
|
10
|
+
# @return [String]
|
|
9
11
|
def name
|
|
10
12
|
"browser"
|
|
11
13
|
end
|
|
12
14
|
|
|
15
|
+
# Tool description
|
|
16
|
+
# @return [String]
|
|
13
17
|
def description
|
|
14
18
|
"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
19
|
end
|
|
16
20
|
|
|
21
|
+
# Parameter schema
|
|
22
|
+
# @return [Hash]
|
|
17
23
|
def parameters
|
|
18
24
|
{
|
|
19
25
|
type: "object",
|
|
@@ -52,6 +58,16 @@ module Pocketrb
|
|
|
52
58
|
}
|
|
53
59
|
end
|
|
54
60
|
|
|
61
|
+
# Execute browser automation action
|
|
62
|
+
# @param action [String] Action to perform (navigate, extract_text, screenshot, click, type, execute_js)
|
|
63
|
+
# @param url [String, nil] URL to navigate to
|
|
64
|
+
# @param selector [String, nil] CSS selector for element targeting
|
|
65
|
+
# @param text [String, nil] Text to type into an element
|
|
66
|
+
# @param javascript [String, nil] JavaScript code to execute in browser context
|
|
67
|
+
# @param screenshot_path [String, nil] File path for saving screenshot
|
|
68
|
+
# @param wait_time [Integer] Milliseconds to wait after navigation
|
|
69
|
+
# @option _kwargs [Object] * Additional action-specific options
|
|
70
|
+
# @return [String] Action result
|
|
55
71
|
def execute(action:, url: nil, selector: nil, text: nil, javascript: nil, screenshot_path: nil, wait_time: 1000,
|
|
56
72
|
**_kwargs)
|
|
57
73
|
case action
|
|
@@ -86,6 +102,9 @@ module Pocketrb
|
|
|
86
102
|
|
|
87
103
|
private
|
|
88
104
|
|
|
105
|
+
# Launch headless browser and yield the page
|
|
106
|
+
# @yield [Playwright::Page] browser page instance
|
|
107
|
+
# @return [Object] result of the block
|
|
89
108
|
def with_browser
|
|
90
109
|
Playwright.create(playwright_cli_executable_path: "npx playwright") do |playwright|
|
|
91
110
|
playwright.chromium.launch(headless: true) do |browser|
|
|
@@ -98,6 +117,10 @@ module Pocketrb
|
|
|
98
117
|
@current_page = nil
|
|
99
118
|
end
|
|
100
119
|
|
|
120
|
+
# Navigate to URL and extract page content
|
|
121
|
+
# @param url [String] URL to navigate to
|
|
122
|
+
# @param wait_time [Integer] milliseconds to wait after navigation
|
|
123
|
+
# @return [String] page title and text content
|
|
101
124
|
def navigate_and_extract(url, wait_time)
|
|
102
125
|
with_browser do |page|
|
|
103
126
|
page.goto(url)
|
|
@@ -110,6 +133,8 @@ module Pocketrb
|
|
|
110
133
|
end
|
|
111
134
|
end
|
|
112
135
|
|
|
136
|
+
# Extract text content from the currently loaded page
|
|
137
|
+
# @return [String] page text content or error if no page is loaded
|
|
113
138
|
def extract_text_from_page
|
|
114
139
|
return error("No page is currently loaded. Use navigate action first.") unless @current_page
|
|
115
140
|
|
|
@@ -117,6 +142,9 @@ module Pocketrb
|
|
|
117
142
|
success(content[0..5000] + (content.length > 5000 ? "...(truncated)" : ""))
|
|
118
143
|
end
|
|
119
144
|
|
|
145
|
+
# Take a screenshot and save to the given path
|
|
146
|
+
# @param path [String] file path to save the screenshot
|
|
147
|
+
# @return [String] confirmation message
|
|
120
148
|
def take_screenshot(path)
|
|
121
149
|
with_browser do |page|
|
|
122
150
|
screenshot_path = resolve_path(path)
|
|
@@ -125,6 +153,10 @@ module Pocketrb
|
|
|
125
153
|
end
|
|
126
154
|
end
|
|
127
155
|
|
|
156
|
+
# Click an element matching the selector
|
|
157
|
+
# @param selector [String] CSS selector for the element
|
|
158
|
+
# @param wait_time [Integer] milliseconds to wait after clicking
|
|
159
|
+
# @return [String] confirmation message
|
|
128
160
|
def click_element(selector, wait_time)
|
|
129
161
|
with_browser do |page|
|
|
130
162
|
page.click(selector)
|
|
@@ -133,6 +165,11 @@ module Pocketrb
|
|
|
133
165
|
end
|
|
134
166
|
end
|
|
135
167
|
|
|
168
|
+
# Type text into an element matching the selector
|
|
169
|
+
# @param selector [String] CSS selector for the input element
|
|
170
|
+
# @param text [String] content to fill into the input element
|
|
171
|
+
# @param wait_time [Integer] milliseconds to wait after typing
|
|
172
|
+
# @return [String] confirmation message
|
|
136
173
|
def type_into_element(selector, text, wait_time)
|
|
137
174
|
with_browser do |page|
|
|
138
175
|
page.fill(selector, text)
|
|
@@ -141,6 +178,9 @@ module Pocketrb
|
|
|
141
178
|
end
|
|
142
179
|
end
|
|
143
180
|
|
|
181
|
+
# Execute JavaScript code in the browser page context
|
|
182
|
+
# @param javascript [String] JavaScript code to evaluate
|
|
183
|
+
# @return [String] execution result
|
|
144
184
|
def execute_javascript(javascript)
|
|
145
185
|
with_browser do |page|
|
|
146
186
|
result = page.evaluate(javascript)
|
|
@@ -6,6 +6,7 @@ module Pocketrb
|
|
|
6
6
|
module Tools
|
|
7
7
|
# Enhanced browser tool with sessions and tabs
|
|
8
8
|
class BrowserAdvanced < Base
|
|
9
|
+
# Available browser actions
|
|
9
10
|
ACTIONS = %w[
|
|
10
11
|
new_tab close_tab focus_tab list_tabs
|
|
11
12
|
navigate back forward refresh
|
|
@@ -17,10 +18,14 @@ module Pocketrb
|
|
|
17
18
|
press_key
|
|
18
19
|
].freeze
|
|
19
20
|
|
|
21
|
+
# Tool name
|
|
22
|
+
# @return [String] Tool identifier
|
|
20
23
|
def name
|
|
21
24
|
"browser"
|
|
22
25
|
end
|
|
23
26
|
|
|
27
|
+
# Tool description
|
|
28
|
+
# @return [String] Human-readable description
|
|
24
29
|
def description
|
|
25
30
|
<<~DESC.strip
|
|
26
31
|
Browse the web with a persistent browser session. Supports multiple tabs,
|
|
@@ -29,6 +34,8 @@ module Pocketrb
|
|
|
29
34
|
DESC
|
|
30
35
|
end
|
|
31
36
|
|
|
37
|
+
# Tool parameters schema
|
|
38
|
+
# @return [Hash] JSON schema for tool parameters
|
|
32
39
|
def parameters
|
|
33
40
|
{
|
|
34
41
|
type: "object",
|
|
@@ -88,11 +95,23 @@ module Pocketrb
|
|
|
88
95
|
}
|
|
89
96
|
end
|
|
90
97
|
|
|
98
|
+
# Check if Playwright is available
|
|
99
|
+
# @return [Boolean] True if npx or playwright command is found
|
|
91
100
|
def available?
|
|
92
101
|
# Check if playwright is available
|
|
93
102
|
system("which npx > /dev/null 2>&1") || system("which playwright > /dev/null 2>&1")
|
|
94
103
|
end
|
|
95
104
|
|
|
105
|
+
# Execute browser operation
|
|
106
|
+
# @param action [String] Action to perform (new_tab, close_tab, focus_tab, list_tabs, navigate, etc.)
|
|
107
|
+
# @option args [String] :url URL for navigation or new tab
|
|
108
|
+
# @option args [String] :tab_id Tab identifier
|
|
109
|
+
# @option args [String] :selector CSS/XPath selector
|
|
110
|
+
# @option args [String] :text Text to type
|
|
111
|
+
# @option args [String] :key Key to press
|
|
112
|
+
# @option args [String] :javascript JavaScript code to execute
|
|
113
|
+
# @option args [Hash] :options Additional action-specific options
|
|
114
|
+
# @return [String] JSON result of operation
|
|
96
115
|
def execute(action:, **args)
|
|
97
116
|
case action
|
|
98
117
|
# Tab management
|
|
@@ -160,10 +179,15 @@ module Pocketrb
|
|
|
160
179
|
|
|
161
180
|
private
|
|
162
181
|
|
|
182
|
+
# Access the browser session singleton
|
|
183
|
+
# @return [BrowserSession]
|
|
163
184
|
def session
|
|
164
185
|
BrowserSession.instance
|
|
165
186
|
end
|
|
166
187
|
|
|
188
|
+
# Get the Playwright page for a tab
|
|
189
|
+
# @param tab_id [String, nil] tab identifier, uses active tab if nil
|
|
190
|
+
# @return [Playwright::Page, nil]
|
|
167
191
|
def page(tab_id = nil)
|
|
168
192
|
if tab_id
|
|
169
193
|
session.tabs[tab_id]&.dig(:page)
|
|
@@ -172,6 +196,8 @@ module Pocketrb
|
|
|
172
196
|
end
|
|
173
197
|
end
|
|
174
198
|
|
|
199
|
+
# Get active page, creating a new tab if none exists
|
|
200
|
+
# @return [Playwright::Page]
|
|
175
201
|
def ensure_page!
|
|
176
202
|
p = page
|
|
177
203
|
return p if p
|
|
@@ -181,8 +207,9 @@ module Pocketrb
|
|
|
181
207
|
session.active_page
|
|
182
208
|
end
|
|
183
209
|
|
|
184
|
-
#
|
|
185
|
-
|
|
210
|
+
# Open a new browser tab, optionally navigating to a URL
|
|
211
|
+
# @param url [String, nil] URL to load in the new tab
|
|
212
|
+
# @return [String] confirmation with tab ID
|
|
186
213
|
def new_tab(url)
|
|
187
214
|
tab_id = session.new_tab(url: url)
|
|
188
215
|
info = session.tab_info(tab_id)
|
|
@@ -194,6 +221,9 @@ module Pocketrb
|
|
|
194
221
|
end
|
|
195
222
|
end
|
|
196
223
|
|
|
224
|
+
# Close a browser tab
|
|
225
|
+
# @param tab_id [String, nil] tab identifier, uses active tab if nil
|
|
226
|
+
# @return [String] confirmation or error message
|
|
197
227
|
def close_tab(tab_id)
|
|
198
228
|
tab_id ||= session.active_tab_id
|
|
199
229
|
return error("No tab to close") unless tab_id
|
|
@@ -206,6 +236,9 @@ module Pocketrb
|
|
|
206
236
|
end
|
|
207
237
|
end
|
|
208
238
|
|
|
239
|
+
# Switch focus to a specific tab
|
|
240
|
+
# @param tab_id [String] tab identifier to focus
|
|
241
|
+
# @return [String] confirmation or error message
|
|
209
242
|
def focus_tab(tab_id)
|
|
210
243
|
return error("Tab ID required") unless tab_id
|
|
211
244
|
|
|
@@ -217,6 +250,8 @@ module Pocketrb
|
|
|
217
250
|
end
|
|
218
251
|
end
|
|
219
252
|
|
|
253
|
+
# List all open browser tabs
|
|
254
|
+
# @return [String] formatted list of open tabs
|
|
220
255
|
def list_tabs
|
|
221
256
|
tabs = session.list_tabs
|
|
222
257
|
return "No tabs open. Use action 'new_tab' to open one." if tabs.empty?
|
|
@@ -232,6 +267,10 @@ module Pocketrb
|
|
|
232
267
|
|
|
233
268
|
# === Navigation ===
|
|
234
269
|
|
|
270
|
+
# Navigate to URL
|
|
271
|
+
# @param url [String] URL to navigate to
|
|
272
|
+
# @option options [String] :wait_until Wait condition (load, domcontentloaded, networkidle)
|
|
273
|
+
# @return [String] Success message
|
|
235
274
|
def navigate(url, options)
|
|
236
275
|
return error("URL required") unless url
|
|
237
276
|
|
|
@@ -245,6 +284,8 @@ module Pocketrb
|
|
|
245
284
|
success("Navigated to: #{title}\nURL: #{p.url}")
|
|
246
285
|
end
|
|
247
286
|
|
|
287
|
+
# Navigate back in browser history
|
|
288
|
+
# @return [String] confirmation message
|
|
248
289
|
def go_back
|
|
249
290
|
p = ensure_page!
|
|
250
291
|
p.go_back
|
|
@@ -252,6 +293,8 @@ module Pocketrb
|
|
|
252
293
|
success("Navigated back to: #{p.title}")
|
|
253
294
|
end
|
|
254
295
|
|
|
296
|
+
# Navigate forward in browser history
|
|
297
|
+
# @return [String] confirmation message
|
|
255
298
|
def go_forward
|
|
256
299
|
p = ensure_page!
|
|
257
300
|
p.go_forward
|
|
@@ -259,6 +302,8 @@ module Pocketrb
|
|
|
259
302
|
success("Navigated forward to: #{p.title}")
|
|
260
303
|
end
|
|
261
304
|
|
|
305
|
+
# Reload the current page
|
|
306
|
+
# @return [String] confirmation message
|
|
262
307
|
def refresh_page
|
|
263
308
|
p = ensure_page!
|
|
264
309
|
p.reload
|
|
@@ -267,6 +312,10 @@ module Pocketrb
|
|
|
267
312
|
|
|
268
313
|
# === Interaction ===
|
|
269
314
|
|
|
315
|
+
# Click an element
|
|
316
|
+
# @param selector [String] CSS or XPath selector
|
|
317
|
+
# @option options [Integer] :timeout Timeout in milliseconds
|
|
318
|
+
# @return [String] Success message
|
|
270
319
|
def click_element(selector, options)
|
|
271
320
|
return error("Selector required") unless selector
|
|
272
321
|
|
|
@@ -277,6 +326,11 @@ module Pocketrb
|
|
|
277
326
|
success("Clicked: #{selector}")
|
|
278
327
|
end
|
|
279
328
|
|
|
329
|
+
# Type text into an element
|
|
330
|
+
# @param selector [String] CSS or XPath selector
|
|
331
|
+
# @param text [String] content to fill into the input element
|
|
332
|
+
# @param _options [Hash] additional options (unused)
|
|
333
|
+
# @return [String] confirmation or error message
|
|
280
334
|
def type_text(selector, text, _options)
|
|
281
335
|
return error("Selector and text required") unless selector && text
|
|
282
336
|
|
|
@@ -286,6 +340,9 @@ module Pocketrb
|
|
|
286
340
|
success("Typed into #{selector}: #{text[0..50]}#{"..." if text.length > 50}")
|
|
287
341
|
end
|
|
288
342
|
|
|
343
|
+
# Hover over an element
|
|
344
|
+
# @param selector [String] CSS or XPath selector
|
|
345
|
+
# @return [String] confirmation or error message
|
|
289
346
|
def hover_element(selector)
|
|
290
347
|
return error("Selector required") unless selector
|
|
291
348
|
|
|
@@ -294,6 +351,10 @@ module Pocketrb
|
|
|
294
351
|
success("Hovering over: #{selector}")
|
|
295
352
|
end
|
|
296
353
|
|
|
354
|
+
# Scroll the page to a position or element
|
|
355
|
+
# @param direction [String, nil] scroll target: top, bottom, or element
|
|
356
|
+
# @param selector [String, nil] CSS selector when scrolling to element
|
|
357
|
+
# @return [String] confirmation or error message
|
|
297
358
|
def scroll_page(direction, selector)
|
|
298
359
|
p = ensure_page!
|
|
299
360
|
|
|
@@ -315,6 +376,10 @@ module Pocketrb
|
|
|
315
376
|
end
|
|
316
377
|
end
|
|
317
378
|
|
|
379
|
+
# Press a keyboard key, optionally targeting an element
|
|
380
|
+
# @param key [String] key to press (e.g. Enter, Tab, Escape)
|
|
381
|
+
# @param selector [String, nil] CSS selector to target, uses keyboard if nil
|
|
382
|
+
# @return [String] confirmation or error message
|
|
318
383
|
def press_key(key, selector)
|
|
319
384
|
return error("Key required") unless key
|
|
320
385
|
|
|
@@ -329,6 +394,9 @@ module Pocketrb
|
|
|
329
394
|
success("Pressed key: #{key}")
|
|
330
395
|
end
|
|
331
396
|
|
|
397
|
+
# Fill multiple form fields at once
|
|
398
|
+
# @param form_data [Hash] mapping of CSS selectors to values
|
|
399
|
+
# @return [String] confirmation or error message
|
|
332
400
|
def fill_form(form_data)
|
|
333
401
|
return error("Form data required") unless form_data.is_a?(Hash)
|
|
334
402
|
|
|
@@ -343,6 +411,10 @@ module Pocketrb
|
|
|
343
411
|
success("Filled #{filled.size} form fields: #{filled.join(", ")}")
|
|
344
412
|
end
|
|
345
413
|
|
|
414
|
+
# Select an option from a dropdown element
|
|
415
|
+
# @param selector [String] CSS selector for the select element
|
|
416
|
+
# @param value [String] option value to select
|
|
417
|
+
# @return [String] confirmation or error message
|
|
346
418
|
def select_option(selector, value)
|
|
347
419
|
return error("Selector and value required") unless selector && value
|
|
348
420
|
|
|
@@ -351,8 +423,9 @@ module Pocketrb
|
|
|
351
423
|
success("Selected '#{value}' in #{selector}")
|
|
352
424
|
end
|
|
353
425
|
|
|
354
|
-
#
|
|
355
|
-
|
|
426
|
+
# Take a screenshot of the current page
|
|
427
|
+
# @param full_page [Boolean, nil] whether to capture the full scrollable page
|
|
428
|
+
# @return [String] path to saved screenshot
|
|
356
429
|
def take_screenshot(full_page)
|
|
357
430
|
p = ensure_page!
|
|
358
431
|
|
|
@@ -364,6 +437,8 @@ module Pocketrb
|
|
|
364
437
|
success("Screenshot saved: #{path}")
|
|
365
438
|
end
|
|
366
439
|
|
|
440
|
+
# Get a text snapshot of the page including content and interactive elements
|
|
441
|
+
# @return [String] formatted page snapshot
|
|
367
442
|
def get_snapshot
|
|
368
443
|
p = ensure_page!
|
|
369
444
|
|
|
@@ -428,6 +503,9 @@ module Pocketrb
|
|
|
428
503
|
SNAPSHOT
|
|
429
504
|
end
|
|
430
505
|
|
|
506
|
+
# Get text content from an element or the entire page
|
|
507
|
+
# @param selector [String, nil] CSS selector, defaults to body if nil
|
|
508
|
+
# @return [String] element text content
|
|
431
509
|
def get_text(selector)
|
|
432
510
|
p = ensure_page!
|
|
433
511
|
|
|
@@ -437,6 +515,10 @@ module Pocketrb
|
|
|
437
515
|
success(text)
|
|
438
516
|
end
|
|
439
517
|
|
|
518
|
+
# Get an HTML attribute value from an element
|
|
519
|
+
# @param selector [String] CSS selector for the element
|
|
520
|
+
# @param attribute [String] attribute name to retrieve
|
|
521
|
+
# @return [String] attribute value or error message
|
|
440
522
|
def get_attribute(selector, attribute)
|
|
441
523
|
return error("Selector and attribute required") unless selector && attribute
|
|
442
524
|
|
|
@@ -445,6 +527,9 @@ module Pocketrb
|
|
|
445
527
|
success("#{selector}[#{attribute}] = #{value}")
|
|
446
528
|
end
|
|
447
529
|
|
|
530
|
+
# Execute JavaScript code in the browser context
|
|
531
|
+
# @param code [String] JavaScript code to evaluate
|
|
532
|
+
# @return [String] execution result or error message
|
|
448
533
|
def execute_javascript(code)
|
|
449
534
|
return error("JavaScript code required") unless code
|
|
450
535
|
|
|
@@ -455,6 +540,11 @@ module Pocketrb
|
|
|
455
540
|
|
|
456
541
|
# === Wait ===
|
|
457
542
|
|
|
543
|
+
# Wait for selector to appear
|
|
544
|
+
# @param selector [String] CSS or XPath selector to wait for
|
|
545
|
+
# @option options [Integer] :timeout Timeout in milliseconds
|
|
546
|
+
# @option options [String] :state Element state to wait for (visible, hidden, attached, detached)
|
|
547
|
+
# @return [String] Success message
|
|
458
548
|
def wait_for(selector, options)
|
|
459
549
|
return error("Selector required") unless selector
|
|
460
550
|
|
|
@@ -21,6 +21,8 @@ module Pocketrb
|
|
|
21
21
|
@started = false
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Start the browser session
|
|
25
|
+
# @return [void]
|
|
24
26
|
def start!
|
|
25
27
|
return if @started
|
|
26
28
|
|
|
@@ -42,6 +44,8 @@ module Pocketrb
|
|
|
42
44
|
end
|
|
43
45
|
end
|
|
44
46
|
|
|
47
|
+
# Stop the browser session and cleanup
|
|
48
|
+
# @return [void]
|
|
45
49
|
def stop!
|
|
46
50
|
@mutex.synchronize do
|
|
47
51
|
@tabs.each_value do |tab|
|
|
@@ -62,11 +66,15 @@ module Pocketrb
|
|
|
62
66
|
end
|
|
63
67
|
end
|
|
64
68
|
|
|
69
|
+
# Check if browser session is running
|
|
70
|
+
# @return [Boolean] True if browser is started
|
|
65
71
|
def started?
|
|
66
72
|
@started
|
|
67
73
|
end
|
|
68
74
|
|
|
69
75
|
# Create a new tab
|
|
76
|
+
# @param url [String, nil] Initial URL to navigate to
|
|
77
|
+
# @return [String] Tab identifier
|
|
70
78
|
def new_tab(url: nil)
|
|
71
79
|
start! unless started?
|
|
72
80
|
|
|
@@ -90,6 +98,8 @@ module Pocketrb
|
|
|
90
98
|
end
|
|
91
99
|
|
|
92
100
|
# Close a tab
|
|
101
|
+
# @param tab_id [String] Tab identifier to close
|
|
102
|
+
# @return [Boolean] True if tab was closed successfully
|
|
93
103
|
def close_tab(tab_id)
|
|
94
104
|
@mutex.synchronize do
|
|
95
105
|
tab = @tabs.delete(tab_id)
|
|
@@ -102,6 +112,8 @@ module Pocketrb
|
|
|
102
112
|
end
|
|
103
113
|
|
|
104
114
|
# Focus a tab
|
|
115
|
+
# @param tab_id [String] Tab identifier to focus
|
|
116
|
+
# @return [Boolean] True if tab was focused successfully
|
|
105
117
|
def focus_tab(tab_id)
|
|
106
118
|
@mutex.synchronize do
|
|
107
119
|
return false unless @tabs.key?(tab_id)
|
|
@@ -120,6 +132,8 @@ module Pocketrb
|
|
|
120
132
|
end
|
|
121
133
|
|
|
122
134
|
# Get tab info
|
|
135
|
+
# @param tab_id [String] Tab identifier
|
|
136
|
+
# @return [Hash, nil] Tab information hash or nil if not found
|
|
123
137
|
def tab_info(tab_id)
|
|
124
138
|
tab = @tabs[tab_id]
|
|
125
139
|
return nil unless tab
|
|
@@ -145,6 +159,8 @@ module Pocketrb
|
|
|
145
159
|
end
|
|
146
160
|
|
|
147
161
|
# Update tab metadata
|
|
162
|
+
# @param tab_id [String] Tab identifier
|
|
163
|
+
# @return [void]
|
|
148
164
|
def update_tab_info(tab_id)
|
|
149
165
|
tab = @tabs[tab_id]
|
|
150
166
|
return unless tab
|
|
@@ -155,6 +171,8 @@ module Pocketrb
|
|
|
155
171
|
|
|
156
172
|
private
|
|
157
173
|
|
|
174
|
+
# Locate the Playwright CLI executable
|
|
175
|
+
# @return [String] path or command to run Playwright
|
|
158
176
|
def find_playwright_cli
|
|
159
177
|
# Try common locations
|
|
160
178
|
["npx playwright", "playwright", "node_modules/.bin/playwright"].each do |cmd|
|
data/lib/pocketrb/tools/cron.rb
CHANGED
|
@@ -6,10 +6,14 @@ module Pocketrb
|
|
|
6
6
|
module Tools
|
|
7
7
|
# Manage scheduled cron jobs - allows agent to be proactive
|
|
8
8
|
class Cron < Base
|
|
9
|
+
# Tool name
|
|
10
|
+
# @return [String] Tool identifier
|
|
9
11
|
def name
|
|
10
12
|
"cron"
|
|
11
13
|
end
|
|
12
14
|
|
|
15
|
+
# Tool description
|
|
16
|
+
# @return [String] Human-readable description
|
|
13
17
|
def description
|
|
14
18
|
<<~DESC.strip
|
|
15
19
|
Manage scheduled tasks. Create reminders, follow-ups, or recurring checks.
|
|
@@ -17,6 +21,8 @@ module Pocketrb
|
|
|
17
21
|
DESC
|
|
18
22
|
end
|
|
19
23
|
|
|
24
|
+
# Tool parameters schema
|
|
25
|
+
# @return [Hash] JSON schema for tool parameters
|
|
20
26
|
def parameters
|
|
21
27
|
{
|
|
22
28
|
type: "object",
|
|
@@ -56,10 +62,21 @@ module Pocketrb
|
|
|
56
62
|
}
|
|
57
63
|
end
|
|
58
64
|
|
|
65
|
+
# Check if cron service is available
|
|
66
|
+
# @return [Boolean] True if cron service is configured
|
|
59
67
|
def available?
|
|
60
68
|
!cron_service.nil?
|
|
61
69
|
end
|
|
62
70
|
|
|
71
|
+
# Execute cron operation
|
|
72
|
+
# @param action [String] Action to perform (add, list, remove, enable, disable)
|
|
73
|
+
# @param name [String, nil] Job name (for add action)
|
|
74
|
+
# @param message [String, nil] Job message/task (for add action)
|
|
75
|
+
# @param schedule_type [String, nil] Schedule type: "at", "every", or "cron" (for add action)
|
|
76
|
+
# @param schedule_value [String, Integer, nil] Schedule value based on type (for add action)
|
|
77
|
+
# @param job_id [String, nil] Job identifier (for remove/enable/disable actions)
|
|
78
|
+
# @param deliver [Boolean] Whether to deliver message via channel (defaults to true)
|
|
79
|
+
# @return [String] JSON result of operation
|
|
63
80
|
def execute(action:, name: nil, message: nil, schedule_type: nil, schedule_value: nil, job_id: nil, deliver: true)
|
|
64
81
|
return error("Cron service not available. Start with --enable-cron flag.") unless cron_service
|
|
65
82
|
|
|
@@ -81,18 +98,31 @@ module Pocketrb
|
|
|
81
98
|
|
|
82
99
|
private
|
|
83
100
|
|
|
101
|
+
# Access cron service from context
|
|
102
|
+
# @return [Object, nil]
|
|
84
103
|
def cron_service
|
|
85
104
|
@context[:cron_service]
|
|
86
105
|
end
|
|
87
106
|
|
|
107
|
+
# Get the default output channel
|
|
108
|
+
# @return [Symbol]
|
|
88
109
|
def current_channel
|
|
89
110
|
@context[:default_channel] || @context[:channel] || :telegram
|
|
90
111
|
end
|
|
91
112
|
|
|
113
|
+
# Get the default chat identifier
|
|
114
|
+
# @return [String, nil]
|
|
92
115
|
def current_chat_id
|
|
93
116
|
@context[:default_chat_id] || @context[:chat_id]
|
|
94
117
|
end
|
|
95
118
|
|
|
119
|
+
# Add a new scheduled job
|
|
120
|
+
# @param name [String] job name
|
|
121
|
+
# @param message [String] message to deliver when job runs
|
|
122
|
+
# @param schedule_type [String] schedule kind: at, every, or cron
|
|
123
|
+
# @param schedule_value [String] schedule value matching the type
|
|
124
|
+
# @param deliver [Boolean] whether to deliver message to channel
|
|
125
|
+
# @return [String] confirmation or error message
|
|
96
126
|
def add_job(name, message, schedule_type, schedule_value, deliver)
|
|
97
127
|
return error("Name required") unless name && !name.empty?
|
|
98
128
|
return error("Message required") unless message && !message.empty?
|
|
@@ -116,6 +146,10 @@ module Pocketrb
|
|
|
116
146
|
success("Created job '#{name}' (ID: #{job.id}). Next run: #{next_run_time || "pending"}")
|
|
117
147
|
end
|
|
118
148
|
|
|
149
|
+
# Build a Schedule object from type and value
|
|
150
|
+
# @param type [String] schedule kind: at, every, or cron
|
|
151
|
+
# @param value [String, nil] schedule value for the given type
|
|
152
|
+
# @return [Pocketrb::Cron::Schedule, String] schedule object or error string
|
|
119
153
|
def build_schedule(type, value)
|
|
120
154
|
case type
|
|
121
155
|
when "at"
|
|
@@ -151,6 +185,8 @@ module Pocketrb
|
|
|
151
185
|
end
|
|
152
186
|
end
|
|
153
187
|
|
|
188
|
+
# List all scheduled jobs with status and next run time
|
|
189
|
+
# @return [String] formatted list of jobs
|
|
154
190
|
def list_jobs
|
|
155
191
|
jobs = cron_service.list_jobs(include_disabled: true)
|
|
156
192
|
|
|
@@ -177,6 +213,9 @@ module Pocketrb
|
|
|
177
213
|
lines.join("\n")
|
|
178
214
|
end
|
|
179
215
|
|
|
216
|
+
# Format a schedule object into a human-readable string
|
|
217
|
+
# @param schedule [Pocketrb::Cron::Schedule] schedule object containing kind and timing details
|
|
218
|
+
# @return [String] human-readable schedule description
|
|
180
219
|
def format_schedule(schedule)
|
|
181
220
|
case schedule.kind
|
|
182
221
|
when :at
|
|
@@ -198,6 +237,9 @@ module Pocketrb
|
|
|
198
237
|
end
|
|
199
238
|
end
|
|
200
239
|
|
|
240
|
+
# Remove a scheduled job
|
|
241
|
+
# @param job_id [String] job identifier
|
|
242
|
+
# @return [String] confirmation or error message
|
|
201
243
|
def remove_job(job_id)
|
|
202
244
|
return error("Job ID required") unless job_id
|
|
203
245
|
|
|
@@ -208,6 +250,10 @@ module Pocketrb
|
|
|
208
250
|
end
|
|
209
251
|
end
|
|
210
252
|
|
|
253
|
+
# Enable or disable a scheduled job
|
|
254
|
+
# @param job_id [String] job identifier
|
|
255
|
+
# @param enabled [Boolean] true to enable, false to disable
|
|
256
|
+
# @return [String] confirmation or error message
|
|
211
257
|
def toggle_job(job_id, enabled)
|
|
212
258
|
return error("Job ID required") unless job_id
|
|
213
259
|
|
|
@@ -4,14 +4,20 @@ module Pocketrb
|
|
|
4
4
|
module Tools
|
|
5
5
|
# Edit a file with search/replace
|
|
6
6
|
class EditFile < Base
|
|
7
|
+
# Tool name
|
|
8
|
+
# @return [String]
|
|
7
9
|
def name
|
|
8
10
|
"edit_file"
|
|
9
11
|
end
|
|
10
12
|
|
|
13
|
+
# Tool description
|
|
14
|
+
# @return [String]
|
|
11
15
|
def description
|
|
12
16
|
"Edit a file by replacing specific text. The old_string must match exactly (including whitespace and indentation). Use for precise edits rather than rewriting entire files."
|
|
13
17
|
end
|
|
14
18
|
|
|
19
|
+
# Parameter schema
|
|
20
|
+
# @return [Hash]
|
|
15
21
|
def parameters
|
|
16
22
|
{
|
|
17
23
|
type: "object",
|
|
@@ -37,6 +43,12 @@ module Pocketrb
|
|
|
37
43
|
}
|
|
38
44
|
end
|
|
39
45
|
|
|
46
|
+
# Execute file edit operation
|
|
47
|
+
# @param path [String] File path to edit
|
|
48
|
+
# @param old_string [String] Exact text to find
|
|
49
|
+
# @param new_string [String] Replacement text
|
|
50
|
+
# @param replace_all [Boolean] Whether to replace all occurrences
|
|
51
|
+
# @return [String] Success or error message
|
|
40
52
|
def execute(path:, old_string:, new_string:, replace_all: false)
|
|
41
53
|
resolved = validate_path!(path)
|
|
42
54
|
|
|
@@ -75,6 +87,10 @@ module Pocketrb
|
|
|
75
87
|
|
|
76
88
|
private
|
|
77
89
|
|
|
90
|
+
# Suggest similar content when old_string is not found
|
|
91
|
+
# @param content [String] full file content
|
|
92
|
+
# @param old_string [String] text that was not found
|
|
93
|
+
# @return [String] suggestion message with closest matches
|
|
78
94
|
def suggest_match(content, old_string)
|
|
79
95
|
# Try to find similar content
|
|
80
96
|
lines = content.lines
|