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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/README.md +98 -17
  4. data/lib/pocketrb/agent/compaction.rb +235 -22
  5. data/lib/pocketrb/agent/context.rb +26 -3
  6. data/lib/pocketrb/agent/loop.rb +53 -5
  7. data/lib/pocketrb/agent/spawn_tool.rb +14 -0
  8. data/lib/pocketrb/agent/subagent_manager.rb +24 -2
  9. data/lib/pocketrb/bus/events.rb +42 -1
  10. data/lib/pocketrb/bus/message_bus.rb +37 -0
  11. data/lib/pocketrb/channels/base.rb +13 -0
  12. data/lib/pocketrb/channels/cli.rb +16 -1
  13. data/lib/pocketrb/channels/telegram.rb +70 -4
  14. data/lib/pocketrb/channels/whatsapp.rb +49 -7
  15. data/lib/pocketrb/cli/chat.rb +7 -0
  16. data/lib/pocketrb/cli/config.rb +4 -0
  17. data/lib/pocketrb/cli/cron.rb +6 -0
  18. data/lib/pocketrb/cli.rb +0 -1
  19. data/lib/pocketrb/config.rb +55 -6
  20. data/lib/pocketrb/cron/job.rb +41 -0
  21. data/lib/pocketrb/cron/service.rb +44 -15
  22. data/lib/pocketrb/heartbeat/service.rb +21 -0
  23. data/lib/pocketrb/mcp/client.rb +41 -3
  24. data/lib/pocketrb/mcp/memory_tool.rb +33 -0
  25. data/lib/pocketrb/media/processor.rb +32 -1
  26. data/lib/pocketrb/memory.rb +12 -3
  27. data/lib/pocketrb/planning/manager.rb +29 -1
  28. data/lib/pocketrb/planning/plan.rb +52 -0
  29. data/lib/pocketrb/planning/tool.rb +45 -0
  30. data/lib/pocketrb/providers/anthropic.rb +113 -9
  31. data/lib/pocketrb/providers/base.rb +39 -4
  32. data/lib/pocketrb/providers/claude_cli.rb +79 -7
  33. data/lib/pocketrb/providers/claude_max_proxy.rb +72 -6
  34. data/lib/pocketrb/providers/openai_compatible.rb +79 -0
  35. data/lib/pocketrb/providers/openrouter.rb +72 -12
  36. data/lib/pocketrb/providers/registry.rb +24 -0
  37. data/lib/pocketrb/providers/ruby_llm_provider.rb +168 -54
  38. data/lib/pocketrb/providers/types.rb +51 -1
  39. data/lib/pocketrb/session/manager.rb +34 -2
  40. data/lib/pocketrb/session/session.rb +53 -11
  41. data/lib/pocketrb/skills/builtin/tmux/SKILL.md +9 -9
  42. data/lib/pocketrb/skills/create_tool.rb +28 -0
  43. data/lib/pocketrb/skills/loader.rb +22 -1
  44. data/lib/pocketrb/skills/modify_tool.rb +22 -0
  45. data/lib/pocketrb/skills/skill.rb +19 -3
  46. data/lib/pocketrb/tools/background_job_manager.rb +29 -0
  47. data/lib/pocketrb/tools/base.rb +16 -1
  48. data/lib/pocketrb/tools/browser.rb +40 -0
  49. data/lib/pocketrb/tools/browser_advanced.rb +94 -4
  50. data/lib/pocketrb/tools/browser_session.rb +18 -0
  51. data/lib/pocketrb/tools/cron.rb +46 -0
  52. data/lib/pocketrb/tools/edit_file.rb +16 -0
  53. data/lib/pocketrb/tools/exec.rb +46 -0
  54. data/lib/pocketrb/tools/jobs.rb +26 -2
  55. data/lib/pocketrb/tools/list_dir.rb +19 -0
  56. data/lib/pocketrb/tools/memory.rb +34 -0
  57. data/lib/pocketrb/tools/message.rb +15 -0
  58. data/lib/pocketrb/tools/para_memory.rb +47 -0
  59. data/lib/pocketrb/tools/read_file.rb +11 -0
  60. data/lib/pocketrb/tools/registry.rb +12 -4
  61. data/lib/pocketrb/tools/send_file.rb +29 -0
  62. data/lib/pocketrb/tools/think.rb +9 -0
  63. data/lib/pocketrb/tools/web_fetch.rb +58 -3
  64. data/lib/pocketrb/tools/web_search.rb +25 -0
  65. data/lib/pocketrb/tools/write_file.rb +10 -0
  66. data/lib/pocketrb/version.rb +2 -1
  67. data/lib/pocketrb.rb +4 -1
  68. data/pocketrb.gemspec +6 -8
  69. metadata +24 -44
@@ -4,9 +4,9 @@ require "telegram/bot"
4
4
 
5
5
  module Pocketrb
6
6
  module Channels
7
- # Telegram channel using long polling
8
- # Simple and reliable - no webhook/public IP needed
7
+ # Telegram channel using long polling. Simple and reliable - no webhook/public IP needed
9
8
  class Telegram < Base
9
+ # Markdown to HTML conversion patterns for Telegram formatting
10
10
  MARKDOWN_TO_HTML = {
11
11
  # Bold **text** or __text__
12
12
  /\*\*(.+?)\*\*/ => '<b>\1</b>',
@@ -19,6 +19,7 @@ module Pocketrb
19
19
  /`([^`]+)`/ => '<code>\1</code>'
20
20
  }.freeze
21
21
 
22
+ # Telegram file download URL template
22
23
  TELEGRAM_FILE_URL = "https://api.telegram.org/file/bot%<token>s/%<path>s"
23
24
 
24
25
  # Special commands that bypass the agent
@@ -26,6 +27,11 @@ module Pocketrb
26
27
 
27
28
  attr_accessor :status_context
28
29
 
30
+ # Initialize Telegram channel
31
+ # @param bus [Bus::MessageBus] Message bus for publishing and consuming messages
32
+ # @param token [String] Telegram bot API token
33
+ # @param allowed_users [Array<String, Integer>, nil] Allowed usernames or user IDs (nil = allow all)
34
+ # @param download_media [Boolean] Whether to download and process media attachments (defaults to true)
29
35
  def initialize(bus:, token:, allowed_users: nil, download_media: true)
30
36
  super(bus: bus, name: :telegram)
31
37
  @token = token
@@ -40,6 +46,7 @@ module Pocketrb
40
46
 
41
47
  protected
42
48
 
49
+ # Start the Telegram bot listener thread using long polling
43
50
  def run_inbound_loop
44
51
  Pocketrb.logger.info("Starting Telegram bot (polling mode)...")
45
52
 
@@ -73,6 +80,8 @@ module Pocketrb
73
80
  raise
74
81
  end
75
82
 
83
+ # Send an outbound message via the Telegram API, with HTML formatting
84
+ # @param message [Bus::OutboundMessage] outbound message to deliver
76
85
  def send_message(message)
77
86
  return unless @bot
78
87
 
@@ -107,6 +116,9 @@ module Pocketrb
107
116
  end
108
117
  end
109
118
 
119
+ # Dispatch a media item to the appropriate Telegram send method
120
+ # @param chat_id [Integer] Telegram chat identifier
121
+ # @param media [Media::Item] media attachment to send
110
122
  def send_media(chat_id, media)
111
123
  case media.type
112
124
  when :image
@@ -122,21 +134,33 @@ module Pocketrb
122
134
  Pocketrb.logger.error("Error sending media: #{e.message}")
123
135
  end
124
136
 
137
+ # Upload and send a photo via the Telegram API
138
+ # @param chat_id [Integer] Telegram chat identifier
139
+ # @param media [Media::Item] image media item
125
140
  def send_photo(chat_id, media)
126
141
  file = Faraday::Multipart::FilePart.new(media.path, media.mime_type, media.filename)
127
142
  @bot.api.send_photo(chat_id: chat_id, photo: file)
128
143
  end
129
144
 
145
+ # Upload and send an audio file via the Telegram API
146
+ # @param chat_id [Integer] Telegram chat identifier
147
+ # @param media [Media::Item] audio media item
130
148
  def send_audio(chat_id, media)
131
149
  file = Faraday::Multipart::FilePart.new(media.path, media.mime_type, media.filename)
132
150
  @bot.api.send_audio(chat_id: chat_id, audio: file)
133
151
  end
134
152
 
153
+ # Upload and send a video file via the Telegram API
154
+ # @param chat_id [Integer] Telegram chat identifier
155
+ # @param media [Media::Item] video media item
135
156
  def send_video(chat_id, media)
136
157
  file = Faraday::Multipart::FilePart.new(media.path, media.mime_type, media.filename)
137
158
  @bot.api.send_video(chat_id: chat_id, video: file)
138
159
  end
139
160
 
161
+ # Upload and send a document file via the Telegram API
162
+ # @param chat_id [Integer] Telegram chat identifier
163
+ # @param media [Media::Item] document media item
140
164
  def send_document(chat_id, media)
141
165
  file = Faraday::Multipart::FilePart.new(media.path, media.mime_type, media.filename)
142
166
  @bot.api.send_document(chat_id: chat_id, document: file)
@@ -144,6 +168,8 @@ module Pocketrb
144
168
 
145
169
  private
146
170
 
171
+ # Process an incoming Telegram message: check allowlist, extract content, publish inbound
172
+ # @param message [Telegram::Bot::Types::Message] raw Telegram message object
147
173
  def handle_telegram_message(message)
148
174
  return unless message.is_a?(::Telegram::Bot::Types::Message)
149
175
  unless message.text || message.caption || message.photo || message.voice || message.document || message.audio || message.video
@@ -192,6 +218,9 @@ module Pocketrb
192
218
  @bus.publish_inbound(inbound)
193
219
  end
194
220
 
221
+ # Build a sender identifier string from a Telegram user
222
+ # @param user [Telegram::Bot::Types::User] Telegram user object
223
+ # @return [String] sender identifier combining user ID and username
195
224
  def build_sender_id(user)
196
225
  if user.username
197
226
  "#{user.id}|#{user.username}"
@@ -200,6 +229,9 @@ module Pocketrb
200
229
  end
201
230
  end
202
231
 
232
+ # Extract text content and media descriptions from a Telegram message
233
+ # @param message [Telegram::Bot::Types::Message] raw Telegram message object
234
+ # @return [String] combined text content
203
235
  def build_content(message)
204
236
  parts = []
205
237
  parts << message.text if message.text
@@ -219,6 +251,9 @@ module Pocketrb
219
251
  parts.empty? ? "[empty message]" : parts.join("\n")
220
252
  end
221
253
 
254
+ # Download all media attachments from a Telegram message
255
+ # @param message [Telegram::Bot::Types::Message] raw Telegram message object
256
+ # @return [Array<Media::Item>] downloaded media items
222
257
  def download_media(message)
223
258
  return [] unless @download_media
224
259
 
@@ -277,6 +312,12 @@ module Pocketrb
277
312
  media
278
313
  end
279
314
 
315
+ # Download a file from Telegram servers by file ID
316
+ # @param file_id [String] Telegram file identifier
317
+ # @param _type [Symbol] media type (unused, kept for interface consistency)
318
+ # @param mime_type [String] MIME type of the file
319
+ # @param filename [String, nil] optional filename override
320
+ # @return [Media::Item, nil] downloaded media item or nil on failure
280
321
  def download_telegram_file(file_id, _type, mime_type, filename = nil)
281
322
  # Get file path from Telegram
282
323
  result = @bot.api.get_file(file_id: file_id)
@@ -300,6 +341,9 @@ module Pocketrb
300
341
  nil
301
342
  end
302
343
 
344
+ # Check if a Telegram user is in the allowlist
345
+ # @param user [Telegram::Bot::Types::User] Telegram user to check
346
+ # @return [Boolean] true if user is allowed or no allowlist is configured
303
347
  def allowed_user?(user)
304
348
  return true if @allowed_users.nil? || @allowed_users.empty?
305
349
 
@@ -310,6 +354,9 @@ module Pocketrb
310
354
  end
311
355
 
312
356
  # Handle special commands that bypass the agent
357
+ # @param text [String] message text to check for commands
358
+ # @param chat_id [Integer] Telegram chat identifier for the reply
359
+ # @return [Boolean] true if a special command was handled
313
360
  def handle_special_command(text, chat_id)
314
361
  cmd = text.strip.split.first&.downcase
315
362
  return false unless SPECIAL_COMMANDS.include?(cmd)
@@ -334,6 +381,8 @@ module Pocketrb
334
381
  true
335
382
  end
336
383
 
384
+ # Build an HTML-formatted status overview for the /status command
385
+ # @return [String] HTML-formatted status text
337
386
  def build_status_response
338
387
  lines = []
339
388
  lines << "━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -388,14 +437,15 @@ module Pocketrb
388
437
  lines.join("\n")
389
438
  end
390
439
 
440
+ # Build an HTML-formatted list of background jobs for the /jobs command
441
+ # @return [String] HTML-formatted jobs text
391
442
  def build_jobs_response
392
443
  jobs_info = get_jobs_summary
393
444
  return "No background jobs." if jobs_info[:jobs].empty?
394
445
 
395
446
  lines = ["<b>📋 Background Jobs</b>", ""]
396
447
 
397
- running = jobs_info[:all].select { |j| j[:running] }
398
- completed = jobs_info[:all].reject { |j| j[:running] }
448
+ running, completed = jobs_info[:all].partition { |j| j[:running] }
399
449
 
400
450
  if running.any?
401
451
  lines << "<b>Running:</b>"
@@ -415,6 +465,8 @@ module Pocketrb
415
465
  lines.join("\n")
416
466
  end
417
467
 
468
+ # Build an HTML-formatted list of cron jobs for the /cron command
469
+ # @return [String] HTML-formatted cron jobs text
418
470
  def build_cron_response
419
471
  cron_info = get_cron_summary
420
472
  return "No scheduled jobs." if cron_info[:all].empty?
@@ -437,6 +489,8 @@ module Pocketrb
437
489
  lines.join("\n")
438
490
  end
439
491
 
492
+ # Build an HTML-formatted help text for the /help command
493
+ # @return [String] HTML-formatted help text
440
494
  def build_help_response
441
495
  <<~HELP
442
496
  <b>🤖 Pocketrb Commands</b>
@@ -455,6 +509,8 @@ module Pocketrb
455
509
  HELP
456
510
  end
457
511
 
512
+ # Detect running Claude CLI processes and return their status
513
+ # @return [Array<String>, nil] status lines or nil if no processes found
458
514
  def get_claude_cli_status
459
515
  # Check if claude processes are running
460
516
  claude_pids = `pgrep -f "claude" 2>/dev/null`.strip.split("\n")
@@ -492,6 +548,8 @@ module Pocketrb
492
548
  lines.empty? ? nil : lines.first(8)
493
549
  end
494
550
 
551
+ # Collect background job counts and descriptions
552
+ # @return [Hash] summary with :summary, :jobs, and :all keys
495
553
  def get_jobs_summary
496
554
  job_manager = @status_context[:job_manager]
497
555
 
@@ -517,6 +575,8 @@ module Pocketrb
517
575
  }
518
576
  end
519
577
 
578
+ # Collect cron job counts and descriptions
579
+ # @return [Hash] summary with :summary, :jobs, and :all keys
520
580
  def get_cron_summary
521
581
  cron_service = @status_context[:cron_service]
522
582
  return { summary: "N/A", jobs: [], all: [] } unless cron_service
@@ -531,6 +591,9 @@ module Pocketrb
531
591
  }
532
592
  end
533
593
 
594
+ # Format a duration in seconds as a human-readable uptime string
595
+ # @param seconds [Numeric] elapsed seconds
596
+ # @return [String] formatted uptime (e.g. "2h 30m")
534
597
  def format_uptime(seconds)
535
598
  seconds = seconds.to_i
536
599
  if seconds < 60
@@ -544,6 +607,9 @@ module Pocketrb
544
607
  end
545
608
  end
546
609
 
610
+ # Convert Markdown text to Telegram-compatible HTML
611
+ # @param text [String, nil] Markdown-formatted text
612
+ # @return [String] HTML-formatted text safe for Telegram
547
613
  def markdown_to_telegram_html(text)
548
614
  return "" if text.nil? || text.empty?
549
615
 
@@ -5,8 +5,7 @@ require "socket"
5
5
 
6
6
  module Pocketrb
7
7
  module Channels
8
- # WhatsApp channel via Node.js bridge WebSocket
9
- # Connects to a whatsapp-web.js bridge running on localhost
8
+ # WhatsApp channel via Node.js bridge WebSocket. Connects to a whatsapp-web.js bridge running on localhost
10
9
  #
11
10
  # Bridge Protocol:
12
11
  # - Receive: {type: "message", sender: "...", content: "...", timestamp: ..., isGroup: bool}
@@ -14,9 +13,16 @@ module Pocketrb
14
13
  # - Status: {type: "status", status: "connected"}
15
14
  # - QR: {type: "qr", qr: "data:image/png;base64,..."}
16
15
  class WhatsApp < Base
16
+ # Delay in seconds before reconnecting after disconnect
17
17
  RECONNECT_DELAY = 5
18
+ # Default WebSocket bridge URL
18
19
  DEFAULT_BRIDGE_URL = "ws://localhost:3001"
19
20
 
21
+ # Initialize WhatsApp channel
22
+ # @param bus [Bus::MessageBus] Message bus for publishing and consuming messages
23
+ # @param bridge_url [String] WebSocket bridge URL (defaults to ws://localhost:3001)
24
+ # @param allowed_users [Array<String>, nil] Allowed phone numbers (nil = allow all)
25
+ # @param download_media [Boolean] Whether to download and process media attachments (defaults to true)
20
26
  def initialize(bus:, bridge_url: DEFAULT_BRIDGE_URL, allowed_users: nil, download_media: true)
21
27
  super(bus: bus, name: :whatsapp)
22
28
  @bridge_url = bridge_url
@@ -28,6 +34,7 @@ module Pocketrb
28
34
  end
29
35
 
30
36
  # Check if we can connect to the bridge
37
+ # @return [Boolean] true if the WebSocket client library is available
31
38
  def available?
32
39
  require "websocket-client-simple"
33
40
  true
@@ -35,11 +42,22 @@ module Pocketrb
35
42
  false
36
43
  end
37
44
 
45
+ # Start the channel
46
+ # @raise [MissingDependencyError] when the websocket-client-simple gem is not installed
47
+ def run
48
+ unless available?
49
+ raise MissingDependencyError,
50
+ "The WhatsApp channel needs the websocket-client-simple gem. " \
51
+ "Add `gem \"websocket-client-simple\"` to your Gemfile or run: gem install websocket-client-simple"
52
+ end
53
+
54
+ super
55
+ end
56
+
38
57
  protected
39
58
 
59
+ # Connect to the WhatsApp bridge and listen for inbound messages
40
60
  def run_inbound_loop
41
- require "websocket-client-simple"
42
-
43
61
  Pocketrb.logger.info("Starting WhatsApp channel (bridge: #{@bridge_url})")
44
62
 
45
63
  loop do
@@ -56,11 +74,10 @@ module Pocketrb
56
74
  sleep RECONNECT_DELAY
57
75
  end
58
76
  end
59
- rescue LoadError
60
- Pocketrb.logger.error("websocket-client-simple gem not installed. Run: gem install websocket-client-simple")
61
- raise
62
77
  end
63
78
 
79
+ # Send an outbound message via the WhatsApp WebSocket bridge
80
+ # @param message [Bus::OutboundMessage] outbound message to deliver
64
81
  def send_message(message)
65
82
  return unless @ws && @connected
66
83
 
@@ -79,6 +96,7 @@ module Pocketrb
79
96
 
80
97
  private
81
98
 
99
+ # Establish WebSocket connection and register event handlers
82
100
  def connect_and_listen
83
101
  channel = self
84
102
 
@@ -106,6 +124,8 @@ module Pocketrb
106
124
  sleep 0.5 while @ws.open? && @running
107
125
  end
108
126
 
127
+ # Route an incoming bridge WebSocket message by type
128
+ # @param data [String] raw JSON string from the bridge
109
129
  def handle_bridge_message(data)
110
130
  message = JSON.parse(data)
111
131
 
@@ -129,6 +149,8 @@ module Pocketrb
129
149
  Pocketrb.logger.warn("WhatsApp: invalid JSON: #{e.message}")
130
150
  end
131
151
 
152
+ # Process an incoming WhatsApp message and publish it to the bus
153
+ # @param message [Hash] parsed bridge message payload
132
154
  def handle_incoming_message(message)
133
155
  sender = extract_phone(message["sender"] || message["from"])
134
156
  return unless allowed_user?(sender)
@@ -171,6 +193,9 @@ module Pocketrb
171
193
  @bus.publish_inbound(inbound)
172
194
  end
173
195
 
196
+ # Download or decode media attached to a WhatsApp message
197
+ # @param message [Hash] parsed bridge message with media data
198
+ # @return [Media::Item, nil] processed media item or nil on failure
174
199
  def process_whatsapp_media(message)
175
200
  return nil unless message["mediaData"] || message["mediaUrl"]
176
201
 
@@ -191,6 +216,9 @@ module Pocketrb
191
216
  nil
192
217
  end
193
218
 
219
+ # Map a MIME type to a file extension
220
+ # @param mime_type [String] MIME type string
221
+ # @return [String] file extension without dot
194
222
  def extension_for_mime(mime_type)
195
223
  case mime_type
196
224
  when %r{^image/jpeg} then "jpg"
@@ -204,6 +232,8 @@ module Pocketrb
204
232
  end
205
233
  end
206
234
 
235
+ # Log a received QR code event from the bridge
236
+ # @param message [Hash] bridge message containing QR data
207
237
  def handle_qr(message)
208
238
  return unless message["qr"]
209
239
 
@@ -211,6 +241,9 @@ module Pocketrb
211
241
  # Could save to file or display in terminal if needed
212
242
  end
213
243
 
244
+ # Check if a phone number is in the allowlist
245
+ # @param phone [String] phone number to check
246
+ # @return [Boolean] true if allowed or no allowlist is configured
214
247
  def allowed_user?(phone)
215
248
  return true if @allowed_users.nil? || @allowed_users.empty?
216
249
 
@@ -218,6 +251,9 @@ module Pocketrb
218
251
  @allowed_users.any? { |allowed| normalize_phone(allowed) == normalized }
219
252
  end
220
253
 
254
+ # Convert a phone number to a WhatsApp JID
255
+ # @param phone [String] phone number or existing JID
256
+ # @return [String] WhatsApp JID (e.g. "1234@s.whatsapp.net")
221
257
  def to_jid(phone)
222
258
  return phone if phone.include?("@")
223
259
 
@@ -226,12 +262,18 @@ module Pocketrb
226
262
  "#{clean}@s.whatsapp.net"
227
263
  end
228
264
 
265
+ # Extract the phone number portion from a WhatsApp JID
266
+ # @param jid [String, nil] WhatsApp JID string
267
+ # @return [String, nil] phone number or original value
229
268
  def extract_phone(jid)
230
269
  return jid unless jid
231
270
 
232
271
  jid.split("@").first
233
272
  end
234
273
 
274
+ # Strip non-digit characters from a phone number for comparison
275
+ # @param phone [String, nil] phone number to normalize
276
+ # @return [String, nil] digits-only phone number or nil
235
277
  def normalize_phone(phone)
236
278
  return nil unless phone
237
279
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "tty-spinner"
4
+
3
5
  module Pocketrb
4
6
  class CLI
5
7
  # Chat command - interactive chat mode
@@ -53,7 +55,12 @@ module Pocketrb
53
55
  content: input
54
56
  )
55
57
 
58
+ spinner = TTY::Spinner.new("[:spinner] Thinking...", format: :dots)
59
+ spinner.auto_spin
60
+
56
61
  response = agent_loop.process_message(msg)
62
+
63
+ spinner.stop
57
64
  puts "\n#{response.content}" if response
58
65
  end
59
66
  end
@@ -5,6 +5,7 @@ module Pocketrb
5
5
  # Config command - manages configuration
6
6
  class Config < Thor
7
7
  desc "show", "Show current configuration"
8
+ # Display current configuration values
8
9
  def show
9
10
  workspace = options[:workspace] || Dir.pwd
10
11
  config = Pocketrb::Config.load(workspace)
@@ -18,6 +19,8 @@ module Pocketrb
18
19
  end
19
20
 
20
21
  desc "set KEY VALUE", "Set a configuration value"
22
+ # @param key [String] Configuration key to set
23
+ # @param value [String] Value to assign (will be auto-converted to int, float, or boolean if applicable)
21
24
  def set(key, value)
22
25
  workspace = options[:workspace] || Dir.pwd
23
26
  config = Pocketrb::Config.load(workspace)
@@ -36,6 +39,7 @@ module Pocketrb
36
39
  end
37
40
 
38
41
  desc "get KEY", "Get a configuration value"
42
+ # @param key [String] Configuration key to retrieve
39
43
  def get(key)
40
44
  workspace = options[:workspace] || Dir.pwd
41
45
  config = Pocketrb::Config.load(workspace)
@@ -6,6 +6,7 @@ module Pocketrb
6
6
  class Cron < Thor
7
7
  desc "list", "List scheduled jobs"
8
8
  option :all, type: :boolean, aliases: "-a", desc: "Include disabled jobs"
9
+ # List all scheduled cron jobs
9
10
  def list
10
11
  workspace = Pathname.new(options[:workspace] || Dir.pwd).expand_path
11
12
  cron_store = workspace.join(".pocketrb", "data", "cron", "jobs.json")
@@ -38,6 +39,7 @@ module Pocketrb
38
39
  option :deliver, type: :boolean, default: false, desc: "Deliver to channel instead of processing"
39
40
  option :channel, type: :string, desc: "Target channel for delivery"
40
41
  option :to, type: :string, desc: "Target chat ID for delivery"
42
+ # Add a new scheduled cron job
41
43
  def add
42
44
  workspace = Pathname.new(options[:workspace] || Dir.pwd).expand_path
43
45
  cron_store = workspace.join(".pocketrb", "data", "cron", "jobs.json")
@@ -84,6 +86,7 @@ module Pocketrb
84
86
  end
85
87
 
86
88
  desc "remove JOB_ID", "Remove a scheduled job"
89
+ # @param job_id [String] Job identifier to remove
87
90
  def remove(job_id)
88
91
  workspace = Pathname.new(options[:workspace] || Dir.pwd).expand_path
89
92
  cron_store = workspace.join(".pocketrb", "data", "cron", "jobs.json")
@@ -101,6 +104,7 @@ module Pocketrb
101
104
  end
102
105
 
103
106
  desc "enable JOB_ID", "Enable a scheduled job"
107
+ # @param job_id [String] Job identifier to enable
104
108
  def enable(job_id)
105
109
  workspace = Pathname.new(options[:workspace] || Dir.pwd).expand_path
106
110
  cron_store = workspace.join(".pocketrb", "data", "cron", "jobs.json")
@@ -118,6 +122,7 @@ module Pocketrb
118
122
  end
119
123
 
120
124
  desc "disable JOB_ID", "Disable a scheduled job"
125
+ # @param job_id [String] Job identifier to disable
121
126
  def disable(job_id)
122
127
  workspace = Pathname.new(options[:workspace] || Dir.pwd).expand_path
123
128
  cron_store = workspace.join(".pocketrb", "data", "cron", "jobs.json")
@@ -135,6 +140,7 @@ module Pocketrb
135
140
  end
136
141
 
137
142
  desc "trigger JOB_ID", "Trigger a job manually"
143
+ # @param _job_id [String] Job identifier to trigger (unused in current implementation)
138
144
  def trigger(_job_id)
139
145
  say "Manual job execution requires running gateway", :yellow
140
146
  say "Use 'pocketrb gateway' and the job will be executed", :yellow
data/lib/pocketrb/cli.rb CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "thor"
4
4
 
5
- # Main namespace for Pocketrb gem
6
5
  module Pocketrb
7
6
  # Command-line interface
8
7
  class CLI < Thor