heathrow 0.7.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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +58 -0
  3. data/README.md +205 -0
  4. data/bin/heathrow +42 -0
  5. data/bin/heathrowd +283 -0
  6. data/docs/ARCHITECTURE.md +1172 -0
  7. data/docs/DATABASE_SCHEMA.md +685 -0
  8. data/docs/DEVELOPMENT_WORKFLOW.md +867 -0
  9. data/docs/DISCORD_SETUP.md +142 -0
  10. data/docs/GMAIL_OAUTH_SETUP.md +120 -0
  11. data/docs/PLUGIN_SYSTEM.md +1370 -0
  12. data/docs/PROJECT_PLAN.md +1022 -0
  13. data/docs/README.md +417 -0
  14. data/docs/REDDIT_SETUP.md +174 -0
  15. data/docs/REPLY_FORWARD.md +182 -0
  16. data/docs/WHATSAPP_TELEGRAM_SETUP.md +306 -0
  17. data/heathrow.gemspec +34 -0
  18. data/heathrowd.service +21 -0
  19. data/img/heathrow.svg +95 -0
  20. data/img/rss_threaded.png +0 -0
  21. data/img/sources.png +0 -0
  22. data/lib/heathrow/address_book.rb +42 -0
  23. data/lib/heathrow/config.rb +332 -0
  24. data/lib/heathrow/database.rb +731 -0
  25. data/lib/heathrow/database_new.rb +392 -0
  26. data/lib/heathrow/event_bus.rb +175 -0
  27. data/lib/heathrow/logger.rb +122 -0
  28. data/lib/heathrow/message.rb +176 -0
  29. data/lib/heathrow/message_composer.rb +399 -0
  30. data/lib/heathrow/message_organizer.rb +774 -0
  31. data/lib/heathrow/migrations/001_initial_schema.rb +248 -0
  32. data/lib/heathrow/notmuch.rb +45 -0
  33. data/lib/heathrow/oauth2_smtp.rb +254 -0
  34. data/lib/heathrow/plugin/base.rb +212 -0
  35. data/lib/heathrow/plugin_manager.rb +141 -0
  36. data/lib/heathrow/poller.rb +93 -0
  37. data/lib/heathrow/smtp_sender.rb +204 -0
  38. data/lib/heathrow/source.rb +39 -0
  39. data/lib/heathrow/sources/base.rb +74 -0
  40. data/lib/heathrow/sources/discord.rb +357 -0
  41. data/lib/heathrow/sources/gmail.rb +294 -0
  42. data/lib/heathrow/sources/imap.rb +198 -0
  43. data/lib/heathrow/sources/instagram.rb +307 -0
  44. data/lib/heathrow/sources/instagram_fetch.py +101 -0
  45. data/lib/heathrow/sources/instagram_send.py +55 -0
  46. data/lib/heathrow/sources/instagram_send_marionette.py +104 -0
  47. data/lib/heathrow/sources/maildir.rb +606 -0
  48. data/lib/heathrow/sources/messenger.rb +212 -0
  49. data/lib/heathrow/sources/messenger_fetch.js +297 -0
  50. data/lib/heathrow/sources/messenger_fetch_marionette.py +138 -0
  51. data/lib/heathrow/sources/messenger_send.js +32 -0
  52. data/lib/heathrow/sources/messenger_send.py +100 -0
  53. data/lib/heathrow/sources/reddit.rb +461 -0
  54. data/lib/heathrow/sources/rss.rb +299 -0
  55. data/lib/heathrow/sources/slack.rb +375 -0
  56. data/lib/heathrow/sources/source_manager.rb +328 -0
  57. data/lib/heathrow/sources/telegram.rb +498 -0
  58. data/lib/heathrow/sources/webpage.rb +207 -0
  59. data/lib/heathrow/sources/weechat.rb +479 -0
  60. data/lib/heathrow/sources/whatsapp.rb +474 -0
  61. data/lib/heathrow/ui/application.rb +8098 -0
  62. data/lib/heathrow/ui/navigation.rb +8 -0
  63. data/lib/heathrow/ui/panes.rb +8 -0
  64. data/lib/heathrow/ui/source_wizard.rb +567 -0
  65. data/lib/heathrow/ui/threaded_view.rb +780 -0
  66. data/lib/heathrow/ui/views.rb +8 -0
  67. data/lib/heathrow/version.rb +3 -0
  68. data/lib/heathrow/wizards/discord_wizard.rb +193 -0
  69. data/lib/heathrow/wizards/slack_wizard.rb +140 -0
  70. data/lib/heathrow.rb +55 -0
  71. metadata +147 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: af875b0f7d9ee70cf0f340207d21b5d367e6a8c98464361560c8b7649b2b6195
4
+ data.tar.gz: 30cc45fe89911cfe787a32a1e36e6d5d7a41a8e407779a2fb73cb0428b1b2e75
5
+ SHA512:
6
+ metadata.gz: b2d822a3c2a09d17e13b8f579f4a91fe896145c303be3c51620dc059888ca710c733c302d705eaa7ecd0c709b79bb4524c831bc7f66aff937f39779b3c886c8a
7
+ data.tar.gz: 7ff0e102006c3bb8c33e527612890832120b67f9e3300840d20419bb04e67c0b50afd9b1896e951025c2abd913fe119b959ddf19554b1eb3a6947209a07341f8
data/.gitignore ADDED
@@ -0,0 +1,58 @@
1
+ # Ruby
2
+ *.gem
3
+ *.rbc
4
+ /.config
5
+ /coverage/
6
+ /InstalledFiles
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/examples.txt
10
+ /test/tmp/
11
+ /test/version_tmp/
12
+ /tmp/
13
+
14
+ # Bundler
15
+ /.bundle/
16
+ /vendor/bundle
17
+ /vendor/
18
+ /lib/bundler/man/
19
+ .bundle/
20
+
21
+ # Chitt specific
22
+ ~/.chitt/
23
+ *.db
24
+ *.db-shm
25
+ *.db-wal
26
+ /logs/
27
+ /attachments/
28
+
29
+ # Environment
30
+ .env
31
+ .env.local
32
+
33
+ # Editor directories
34
+ .vscode/
35
+ .idea/
36
+ *.swp
37
+ *.swo
38
+ *~
39
+
40
+ # OS
41
+ .DS_Store
42
+ Thumbs.db
43
+
44
+ # Debug logs
45
+ debug.log
46
+ *.log
47
+ *.bak
48
+
49
+ # Personal test/setup scripts (contain credentials)
50
+ test_*.rb
51
+ setup_*.rb
52
+ import_*.rb
53
+ update_*.rb
54
+ workspace_login.rb
55
+ CLAUDE.md
56
+ .claude/
57
+ node_modules/
58
+ package*.json
data/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # Heathrow
2
+
3
+ <img src="img/heathrow.svg" align="right" width="150">
4
+
5
+ **Where all your messages connect.**
6
+
7
+ ![Ruby](https://img.shields.io/badge/language-Ruby-red) [![Gem Version](https://badge.fury.io/rb/heathrow.svg)](https://badge.fury.io/rb/heathrow) ![Unlicense](https://img.shields.io/badge/license-Unlicense-green) [![Documentation](https://img.shields.io/badge/docs-comprehensive-blue)](docs/) ![Stay Amazing](https://img.shields.io/badge/Stay-Amazing-important)
8
+
9
+ A unified TUI for all your communication. Like Heathrow Airport, every message routes through one hub. Replace mutt, newsboat, weechat, and a dozen chat apps with one terminal interface.
10
+
11
+ ## The Problem
12
+
13
+ "Dad, I sent you a message." Which app? Email? WhatsApp? Discord? Telegram? SMS? Nobody should have to chase 4-5 apps to find a message from their kid.
14
+
15
+ ## The Solution
16
+
17
+ All sources in one place. Create your custom views. Full keyboard control.
18
+
19
+ ![RSS Threaded View](img/rss_threaded.png)
20
+
21
+ ## Background
22
+
23
+ In 2007 I had the idea of One Communication Hub to rule them all. Now, with Claude Code, it was efficient to realize that idea. It has now replaced [mutt](http://mutt.org/) that I've been using since 2003, my RSS readers ([newsboat](https://newsboat.org/index.html) of late) and many other apps. Heathrow now functions as my communication hub :)
24
+
25
+ ## Features
26
+
27
+ **Sources** (two-way read + reply):
28
+ - Email via Maildir (works with offlineimap, mbsync, fetchmail, gmail_fetch)
29
+ - IRC/Slack via WeeChat relay
30
+ - Discord, Telegram, Instagram DMs, Messenger
31
+ - Reddit
32
+
33
+ **Sources** (read-only):
34
+ - RSS/Atom feeds
35
+ - Web page change monitoring
36
+
37
+ **Core:**
38
+ - Threaded, flat, and folder-grouped view modes
39
+ - 10+ custom filtered views with AND/OR logic
40
+ - Compose, reply, forward with address book aliases (mutt-style)
41
+ - Postpone/recall drafts
42
+ - Dynamic message loading with jump-to-date
43
+ - Hidden HTML link extraction (SharePoint, OneDrive, etc.)
44
+ - Full-text search via notmuch
45
+ - Per-source colors, per-view threading, tag/star highlighting
46
+ - RTFM file picker for attachments
47
+ - Configurable editor args, SMTP, OAuth2
48
+ - AI assistant integration (Claude Code)
49
+ - First-time onboarding wizard
50
+
51
+ ![Source Management](img/sources.png)
52
+
53
+ ## Installation
54
+
55
+ ### Requirements
56
+
57
+ - Ruby >= 2.7
58
+ - [rcurses](https://github.com/isene/rcurses) gem
59
+ - sqlite3 gem
60
+
61
+ ### From RubyGems
62
+
63
+ ```
64
+ gem install heathrow
65
+ ```
66
+
67
+ ### From Source
68
+
69
+ ```
70
+ git clone https://github.com/isene/heathrow.git
71
+ cd heathrow
72
+ gem install rcurses sqlite3
73
+ ./bin/heathrow
74
+ ```
75
+
76
+ ## Quick Start
77
+
78
+ 1. Run `heathrow`
79
+ 2. The onboarding wizard guides you through adding your first source
80
+ 3. Or press `S` to manage sources, `a` to add one
81
+
82
+ ## Key Bindings
83
+
84
+ Press `?` for full help. Here are the essentials:
85
+
86
+ ### Navigation
87
+ | Key | Action |
88
+ |-----|--------|
89
+ | `j`/`k` or arrows | Move up/down |
90
+ | `Enter` | Open message |
91
+ | `PgDn`/`PgUp` | Page through messages |
92
+ | `Home`/`End` | First/last message |
93
+ | `J` | Jump to date (yyyy-mm-dd) |
94
+ | `n`/`p` | Next/previous unread |
95
+
96
+ ### Views
97
+ | Key | Action |
98
+ |-----|--------|
99
+ | `A` | All messages |
100
+ | `N` | New (unread) |
101
+ | `S` | Sources management |
102
+ | `0-9`, `F1-F12` | Custom filtered views |
103
+ | `G` | Cycle view mode (flat/threaded/folders) |
104
+ | `B` | Browse all folders |
105
+ | `F` | Browse favorite folders |
106
+
107
+ ### Message Actions
108
+ | Key | Action |
109
+ |-----|--------|
110
+ | `r` | Reply |
111
+ | `e` | Reply with editor |
112
+ | `g` | Reply all |
113
+ | `f` | Forward |
114
+ | `m` | Compose new |
115
+ | `E` | Edit message as new |
116
+ | `R` | Toggle read/unread |
117
+ | `M` | Mark all in view as read |
118
+ | `*` | Toggle star |
119
+ | `t`/`T` | Tag message / tag all |
120
+ | `d` | Mark for deletion |
121
+ | `<` | Purge deleted |
122
+ | `x` | Open in browser |
123
+ | `v` | View attachments |
124
+ | `Y` | Copy right pane to clipboard |
125
+ | `y` | Copy message ID to clipboard |
126
+
127
+ ### Compose Prompt
128
+ | Key | Action |
129
+ |-----|--------|
130
+ | `Enter` | Send |
131
+ | `e` | Edit (re-open editor) |
132
+ | `a` | Attach files (via RTFM picker) |
133
+ | `p` | Postpone (save as draft) |
134
+ | `ESC` | Cancel |
135
+
136
+ ### UI
137
+ | Key | Action |
138
+ |-----|--------|
139
+ | `w` | Cycle pane width |
140
+ | `Ctrl-b` | Cycle border style |
141
+ | `D` | Cycle date format |
142
+ | `o` | Cycle sort order |
143
+ | `P` | Preferences popup |
144
+ | `?` | Help (press again for extended) |
145
+ | `q` | Quit |
146
+
147
+ ## Configuration
148
+
149
+ All settings live in `~/.heathrow/heathrowrc` (Ruby syntax):
150
+
151
+ ```ruby
152
+ # UI
153
+ set :color_theme, 'Mutt'
154
+ set :date_format, '%b %-d'
155
+ set :sort_order, 'latest'
156
+ set :pane_width, 3
157
+ set :border_style, 1
158
+
159
+ # SMTP / OAuth2
160
+ set :default_email, 'you@example.com'
161
+ set :smtp_command, '~/bin/gmail_smtp'
162
+ set :safe_dir, '~/.heathrow/mail'
163
+ set :oauth2_script, '~/bin/oauth2.py'
164
+ set :oauth2_domains, %w[gmail.com]
165
+
166
+ # Identities (auto-selected by folder)
167
+ identity 'default',
168
+ from: 'You <you@example.com>',
169
+ signature: '~/.signature',
170
+ smtp: '~/bin/gmail_smtp'
171
+
172
+ # Custom views
173
+ view '1', 'Personal', folder: 'Personal'
174
+ view '2', 'Work', folder: 'Work'
175
+ view '9', 'RSS', source_type: 'rss'
176
+
177
+ # Favorite folders
178
+ set :favorite_folders, %w[Personal Work Archive]
179
+ ```
180
+
181
+ Most settings are also available via the `P` preferences popup.
182
+
183
+ ## Architecture
184
+
185
+ ```
186
+ +-- Heathrow
187
+ +-- rcurses TUI (panes, input, rendering)
188
+ +-- SQLite database (messages, sources, views, settings)
189
+ +-- Source plugins (maildir, rss, weechat, discord, ...)
190
+ +-- Message organizer (threading, grouping, sorting)
191
+ +-- Background poller (per-source sync intervals)
192
+ +-- Composer (editor integration, address book, SMTP)
193
+ ```
194
+
195
+ Each source is a self-contained plugin. Sources crash independently without affecting the core. Filters default to "show all" on failure.
196
+
197
+ ## Credits
198
+
199
+ - Created by Geir Isene with [Claude Code](https://claude.ai/claude-code)
200
+ - Built on [rcurses](https://github.com/isene/rcurses) TUI library
201
+ - Inspired by [RTFM](https://github.com/isene/RTFM) file manager
202
+
203
+ ## License
204
+
205
+ [Unlicense](https://unlicense.org/) - released into the public domain.
data/bin/heathrow ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Heathrow - Communication Hub In The Terminal
5
+ # A unified TUI for all your communication sources
6
+ # Author: Geir Isene with Claude Code
7
+ # License: Public Domain
8
+
9
+ CRASH_LOG = '/tmp/heathrow-crash.log'
10
+
11
+ require_relative '../lib/heathrow'
12
+
13
+ begin
14
+ heathrow = Heathrow::Application.new
15
+ heathrow.run
16
+ rescue Interrupt
17
+ # Restore terminal
18
+ system('stty sane 2>/dev/null')
19
+ print "\e[?25h\e[0m\e[2J\e[H"
20
+ $stdout.flush
21
+ exit 0
22
+ rescue => e
23
+ # Log full crash details
24
+ crash = "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} CRASH in #{e.backtrace&.first}\n"
25
+ crash << " #{e.class}: #{e.message}\n"
26
+ crash << " Backtrace:\n"
27
+ e.backtrace&.first(20)&.each { |l| crash << " #{l}\n" }
28
+ crash << "\n"
29
+ File.write(CRASH_LOG, crash, mode: 'a')
30
+
31
+ # Restore terminal fully (stty sane handles raw mode reset)
32
+ system('stty sane 2>/dev/null')
33
+ print "\e[?25h\e[0m\e[2J\e[H"
34
+ $stdout.flush
35
+
36
+ # Print to stderr (rsh captures this better than stdout)
37
+ loc = e.backtrace&.first&.sub(/^.*lib\/heathrow\//, '')
38
+ $stderr.puts "Heathrow crashed: #{e.class}: #{e.message}"
39
+ $stderr.puts " at #{loc}"
40
+ $stderr.puts " Full log: #{CRASH_LOG}"
41
+ exit 1
42
+ end
data/bin/heathrowd ADDED
@@ -0,0 +1,283 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Heathrow Daemon - Background polling service
5
+ # This runs independently of any Heathrow UI instances
6
+ # Author: Geir Isene with Claude Code
7
+ # License: Public Domain
8
+
9
+ require 'fileutils'
10
+ require 'logger'
11
+ require 'optparse'
12
+ require_relative '../lib/heathrow'
13
+
14
+ class HeathrowDaemon
15
+ def initialize(options = {})
16
+ @options = {
17
+ daemonize: false,
18
+ pid_file: File.join(HEATHROW_HOME, 'heathrowd.pid'),
19
+ log_file: File.join(HEATHROW_LOGS, 'heathrowd.log'),
20
+ poll_interval: 30 # Default 30 seconds between polling cycles
21
+ }.merge(options)
22
+
23
+ setup_logging
24
+ setup_components
25
+ end
26
+
27
+ def start
28
+ if running?
29
+ @logger.info "Heathrow daemon already running (PID: #{read_pid})"
30
+ puts "Heathrow daemon already running"
31
+ return false
32
+ end
33
+
34
+ if @options[:daemonize]
35
+ daemonize
36
+ end
37
+
38
+ write_pid
39
+ setup_signal_handlers
40
+
41
+ @logger.info "Starting Heathrow daemon (PID: #{Process.pid})"
42
+ puts "Starting Heathrow daemon..." unless @options[:daemonize]
43
+
44
+ run_daemon
45
+ end
46
+
47
+ def stop
48
+ unless running?
49
+ puts "Heathrow daemon not running"
50
+ return false
51
+ end
52
+
53
+ pid = read_pid
54
+ begin
55
+ Process.kill('TERM', pid)
56
+ puts "Stopping Heathrow daemon (PID: #{pid})"
57
+
58
+ # Wait for process to stop
59
+ 30.times do
60
+ sleep 0.1
61
+ break unless running?
62
+ end
63
+
64
+ if running?
65
+ Process.kill('KILL', pid)
66
+ puts "Force killed Heathrow daemon"
67
+ end
68
+
69
+ cleanup_pid
70
+ true
71
+ rescue Errno::ESRCH
72
+ # Process already stopped
73
+ cleanup_pid
74
+ puts "Heathrow daemon was not running"
75
+ false
76
+ end
77
+ end
78
+
79
+ def status
80
+ if running?
81
+ pid = read_pid
82
+ puts "Heathrow daemon running (PID: #{pid})"
83
+ true
84
+ else
85
+ puts "Heathrow daemon not running"
86
+ false
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def setup_logging
93
+ @logger = Logger.new(@options[:log_file])
94
+ @logger.level = Logger::INFO
95
+ @logger.formatter = proc do |severity, datetime, progname, msg|
96
+ "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity}: #{msg}\n"
97
+ end
98
+ end
99
+
100
+ def setup_components
101
+ @db = Heathrow::Database.new
102
+ @plugin_manager = Heathrow::PluginManager.new(@db)
103
+ @running = false
104
+ end
105
+
106
+ def daemonize
107
+ # Fork and detach from terminal
108
+ Process.fork && exit
109
+ Process.setsid
110
+ Process.fork && exit
111
+
112
+ # Redirect standard streams
113
+ STDIN.reopen('/dev/null')
114
+ STDOUT.reopen('/dev/null', 'w')
115
+ STDERR.reopen('/dev/null', 'w')
116
+ end
117
+
118
+ def write_pid
119
+ File.write(@options[:pid_file], Process.pid.to_s)
120
+ end
121
+
122
+ def read_pid
123
+ return nil unless File.exist?(@options[:pid_file])
124
+ File.read(@options[:pid_file]).strip.to_i
125
+ end
126
+
127
+ def cleanup_pid
128
+ File.delete(@options[:pid_file]) if File.exist?(@options[:pid_file])
129
+ end
130
+
131
+ def running?
132
+ return false unless File.exist?(@options[:pid_file])
133
+
134
+ pid = read_pid
135
+ return false unless pid && pid > 0
136
+
137
+ begin
138
+ Process.kill(0, pid)
139
+ true
140
+ rescue Errno::ESRCH
141
+ # Process doesn't exist
142
+ cleanup_pid
143
+ false
144
+ end
145
+ end
146
+
147
+ def setup_signal_handlers
148
+ %w[TERM INT].each do |signal|
149
+ Signal.trap(signal) do
150
+ @logger.info "Received #{signal} signal, shutting down..."
151
+ @running = false
152
+ end
153
+ end
154
+
155
+ Signal.trap('HUP') do
156
+ @logger.info "Received HUP signal, reloading configuration..."
157
+ # Could reload config here if needed
158
+ end
159
+ end
160
+
161
+ def run_daemon
162
+ @running = true
163
+ @logger.info "Daemon started, polling every #{@options[:poll_interval]} seconds"
164
+
165
+ while @running
166
+ begin
167
+ poll_all_sources
168
+ sleep @options[:poll_interval]
169
+ rescue StandardError => e
170
+ @logger.error "Error in daemon loop: #{e.message}"
171
+ @logger.error e.backtrace.join("\n")
172
+ sleep 5 # Brief pause on error
173
+ end
174
+ end
175
+
176
+ @logger.info "Daemon shutting down"
177
+ cleanup_pid
178
+ end
179
+
180
+ def poll_all_sources
181
+ sources = @db.get_sources(true) # Only enabled sources
182
+ @logger.debug "Checking #{sources.size} enabled sources"
183
+
184
+ sources.each do |source_data|
185
+ begin
186
+ poll_source(source_data)
187
+ rescue StandardError => e
188
+ @logger.error "Error polling source #{source_data['name']}: #{e.message}"
189
+ end
190
+ end
191
+ end
192
+
193
+ def poll_source(source_data)
194
+ source = Heathrow::Source.new(
195
+ id: source_data['id'],
196
+ type: source_data['type'],
197
+ name: source_data['name'],
198
+ config: source_data['config'],
199
+ enabled: source_data['enabled'] == 1,
200
+ poll_interval: source_data['poll_interval'],
201
+ last_poll: source_data['last_poll']
202
+ )
203
+
204
+ return unless source.should_poll?
205
+
206
+ @logger.debug "Polling source: #{source.name}"
207
+
208
+ plugin = @plugin_manager.create_source(source.type, source)
209
+ return unless plugin
210
+
211
+ messages = plugin.fetch_messages
212
+ new_message_count = 0
213
+
214
+ messages.each do |msg_data|
215
+ message = Heathrow::Message.new(msg_data)
216
+ message.source_id = source.id
217
+ message.source_type = source.type
218
+
219
+ if @db.insert_message(message.to_h.values)
220
+ new_message_count += 1
221
+ end
222
+ end
223
+
224
+ @db.update_source_poll_time(source.id)
225
+
226
+ if new_message_count > 0
227
+ @logger.info "Fetched #{new_message_count} new messages from #{source.name}"
228
+ else
229
+ @logger.debug "No new messages from #{source.name}"
230
+ end
231
+ end
232
+ end
233
+
234
+ # Command line interface
235
+ if __FILE__ == $0
236
+ options = {}
237
+
238
+ OptionParser.new do |opts|
239
+ opts.banner = "Usage: heathrowd [options] {start|stop|restart|status}"
240
+
241
+ opts.on("-d", "--daemon", "Run as daemon") do
242
+ options[:daemonize] = true
243
+ end
244
+
245
+ opts.on("-i", "--interval SECONDS", Integer, "Polling interval (default: 30)") do |interval|
246
+ options[:poll_interval] = interval
247
+ end
248
+
249
+ opts.on("-p", "--pid FILE", "PID file location") do |pid_file|
250
+ options[:pid_file] = pid_file
251
+ end
252
+
253
+ opts.on("-l", "--log FILE", "Log file location") do |log_file|
254
+ options[:log_file] = log_file
255
+ end
256
+
257
+ opts.on("-h", "--help", "Show this help") do
258
+ puts opts
259
+ exit
260
+ end
261
+ end.parse!
262
+
263
+ command = ARGV[0]
264
+ unless %w[start stop restart status].include?(command)
265
+ puts "Usage: heathrowd [options] {start|stop|restart|status}"
266
+ exit 1
267
+ end
268
+
269
+ daemon = HeathrowDaemon.new(options)
270
+
271
+ case command
272
+ when 'start'
273
+ daemon.start
274
+ when 'stop'
275
+ daemon.stop
276
+ when 'restart'
277
+ daemon.stop
278
+ sleep 1
279
+ daemon.start
280
+ when 'status'
281
+ daemon.status
282
+ end
283
+ end