last-resort 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .DS_Store
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ # Specify your gem's dependencies in last-resort.gemspec
4
+ gemspec
5
+
data/Gemfile.lock ADDED
@@ -0,0 +1,46 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ last-resort (0.0.1)
5
+ activesupport (~> 3.2)
6
+ gli (~> 1.6)
7
+ oauth (~> 0.4)
8
+ sinatra (~> 1.3)
9
+ thor (~> 0.14)
10
+ twilio-ruby (~> 3.6)
11
+
12
+ GEM
13
+ remote: http://rubygems.org/
14
+ specs:
15
+ activesupport (3.2.3)
16
+ i18n (~> 0.6)
17
+ multi_json (~> 1.0)
18
+ awesome_print (1.0.2)
19
+ builder (3.0.0)
20
+ gli (1.6.0)
21
+ i18n (0.6.0)
22
+ json (1.6.6)
23
+ jwt (0.1.4)
24
+ json (>= 1.2.4)
25
+ multi_json (1.3.1)
26
+ oauth (0.4.5)
27
+ rack (1.4.1)
28
+ rack-protection (1.2.0)
29
+ rack
30
+ sinatra (1.3.2)
31
+ rack (~> 1.3, >= 1.3.6)
32
+ rack-protection (~> 1.2)
33
+ tilt (~> 1.3, >= 1.3.3)
34
+ thor (0.14.6)
35
+ tilt (1.3.3)
36
+ twilio-ruby (3.6.0)
37
+ builder (>= 2.1.2)
38
+ jwt (>= 0.1.2)
39
+ multi_json (>= 1.0.3)
40
+
41
+ PLATFORMS
42
+ ruby
43
+
44
+ DEPENDENCIES
45
+ awesome_print
46
+ last-resort!
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Scott Hyndman, Ian Ha, Victor Mota
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ Last Resort is a Ruby gem for monitoring critical emails sent by automated services ("Server down!",
2
+ "Resource limit matched!", etc.) and calling your phone to tell you about it. It can be deployed in a
3
+ reliable environment and perform up to 1500 emergency calls for free, using the free and trial tiers
4
+ available from [context.io](http://context.io), [twilio](http://twilio.com) and [heroku](http://heroku.com).
5
+
6
+ ### Installation
7
+
8
+ ```sh
9
+ $ gem install last-resort
10
+ ```
11
+
12
+ ### Getting started
13
+
14
+ ```sh
15
+ $ last-resort new my-awesome-project
16
+ ```
17
+ This will create a scheduling project with a sample `my-awesome-project/config.rb` file.
18
+
19
+ ### Example config.rb file
20
+
21
+ ```ruby
22
+ configure :host => "",
23
+ :twilio_sid => "",
24
+ :twilio_auth_token => "",
25
+ :contextio_account => "",
26
+ :contextio_key => "",
27
+ :contextio_secret => ""
28
+
29
+ # DEFINE YOUR CONTACTS
30
+
31
+ contact :ian, ""
32
+ contact :scott, ""
33
+ contact :victor, ""
34
+
35
+ # DEFINE WHAT EMAILS YOU WANT TO WATCH FOR
36
+
37
+ match :subject => /server down/ # rackspace ping
38
+ match :subject => /resource limit reached/ # monit
39
+
40
+ # DEFINE WHO TO CALL AND WHEN
41
+
42
+ between 19..22, :on => [:wednesday, :thursday] do
43
+ call :victor
44
+ end
45
+
46
+ between :off_hours, :on => :weekdays do
47
+ call :scott
48
+ end
49
+
50
+ between :all_hours, :on => :weekends do
51
+ call [:ian, :scott, :victor]
52
+ end
53
+ ```
54
+
55
+ ### Credit
56
+ Victor Mota ([@vimota](http://www.twitter.com/vimota))
57
+ Scott Hyndman ([@scotthyndman](http://www.twitter.com/scotthyndman))
58
+ Ian Ha ([@ianpha](http://www.twitter.com/ianpha))
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/last-resort ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Parse options
4
+
5
+ # Perform command
data/config/config.rb ADDED
@@ -0,0 +1,35 @@
1
+ configure :host => "",
2
+ :twilio_sid => "",
3
+ :twilio_auth_token => "",
4
+ :contextio_account => "",
5
+ :contextio_key => "",
6
+ :contextio_secret => ""
7
+
8
+
9
+ # DEFINE YOUR CONTACTS
10
+
11
+ contact :ian, ""
12
+ contact :scott, ""
13
+ contact :victor, ""
14
+
15
+
16
+ # DEFINE WHAT EMAILS YOU WANT TO WATCH FOR
17
+
18
+ match :subject => /server down/ # rackspace ping
19
+ match :subject => /resource limit reached/ # monit
20
+
21
+
22
+ # DEFINE WHO TO CALL AND WHEN
23
+
24
+ between 19..22, :on => [:wednesday, :thursday] do
25
+ call :victor
26
+ end
27
+
28
+ between :off_hours, :on => :weekdays do
29
+ call :scott
30
+ end
31
+
32
+ between :all_hours, :on => :weekends do
33
+ call [:ian, :scott, :victor]
34
+ end
35
+
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/last-resort/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Ian Ha", "Victor Mota", "Scott Hyndman"]
6
+ gem.email = ["ianha0@gmail.com", "vimota@gmail.com", "scotty.hyndman@gmail.com"]
7
+ gem.description = "Call your phone when critical emails arrive" # TODO Improve this
8
+ gem.summary = "Call your phone when critical emails arrive" # TODO Improve this
9
+ gem.homepage = ""
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "last-resort"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Last::Resort::VERSION
17
+
18
+ gem.add_dependency "sinatra", "~> 1.3"
19
+ gem.add_dependency "twilio-ruby", "~> 3.6"
20
+ gem.add_dependency "activesupport", "~> 3.2"
21
+ gem.add_dependency "thor", "~> 0.14"
22
+ gem.add_dependency "oauth", "~> 0.4"
23
+ gem.add_dependency "gli", "~> 1.6"
24
+ gem.add_development_dependency "awesome_print"
25
+ end
@@ -0,0 +1,74 @@
1
+ module LastResort
2
+ class Config
3
+
4
+ CONFIG_PATH = "#{PROJECT_ROOT}/config/config.rb"
5
+
6
+ attr_accessor :host,
7
+ :contacts,
8
+ :matchers,
9
+ :schedules,
10
+ :twilio_sid, :twilio_auth_token,
11
+ :contextio_account, :contextio_key, :contextio_secret
12
+
13
+ def initialize
14
+ @contacts = {}
15
+ @matchers = []
16
+ @schedules = []
17
+ run_config_in_context
18
+ end
19
+
20
+ protected
21
+
22
+ def run_config_in_context
23
+ source = open(CONFIG_PATH).read
24
+ self.instance_eval source
25
+ end
26
+
27
+ def configure params
28
+ @host = params[:host]
29
+ @twilio_sid = params[:twilio_sid]
30
+ @twilio_auth_token = params[:twilio_auth_token]
31
+ @contextio_key = params[:contextio_key]
32
+ @contextio_secret = params[:contextio_secret]
33
+ @contextio_account = params[:contextio_account]
34
+ end
35
+
36
+ def contact name, phone
37
+ @contacts[name] = { :name => name, :phone => scrub_phone(phone) }
38
+ end
39
+
40
+ def match matcher
41
+ @matchers << matcher
42
+ end
43
+
44
+ def between hours, options
45
+ hours = hours.is_a?(Array) ? hours : [hours]
46
+
47
+ days = options[:on] || :everyday
48
+ days = days.is_a?(Array) ? days : [days]
49
+
50
+ @current_schedule = {
51
+ :hours => hours,
52
+ :days => days
53
+ }
54
+
55
+ yield
56
+
57
+ @schedules << @current_schedule
58
+ @current_schedule = nil
59
+ end
60
+
61
+ def call contacts
62
+ contacts = contacts.is_a?(Array) ? contacts : [contacts]
63
+ @current_schedule[:contacts] = contacts
64
+ end
65
+
66
+ def scrub_phone phone
67
+ phone.gsub!(/\D/, '')
68
+ phone = "+1#{phone}" unless phone.start_with? "+1"
69
+ phone
70
+ end
71
+ end
72
+ end
73
+
74
+ CONFIG = LastResort::Config.new
@@ -0,0 +1,66 @@
1
+ require 'oauth'
2
+ require 'json'
3
+ require 'net/http'
4
+
5
+ module ContextIO
6
+ class Connection
7
+ VERSION = "2.0"
8
+
9
+ def initialize(key='', secret='', server='https://api.context.io')
10
+ @consumer = OAuth::Consumer.new(key, secret, {:site => server, :sheme => :header})
11
+ @token = OAuth::AccessToken.new @consumer
12
+ end
13
+
14
+ def createWebhook(accountId, parameters)
15
+ post accountId, 'webhooks', parameters
16
+ end
17
+
18
+ def listWebhooks(accountId)
19
+ get accountId, 'webhooks'
20
+ end
21
+
22
+ def deleteWebhook(accountId, webhook_id)
23
+ delete accountId, "webhooks/#{webhook_id}"
24
+ end
25
+
26
+ def deleteAllWebhooks(accountId)
27
+ webhooks = listWebhooks(accountId)
28
+ webhooks.each do |webhook|
29
+ deleteWebhook accountId, webhook["webhook_id"]
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def url(accountId, url, *args)
36
+ if args.empty?
37
+ "/#{VERSION}/accounts/#{accountId}/#{url}"
38
+ else
39
+ "/#{VERSION}/accounts/#{accountId}/#{url}?#{parametrize args}"
40
+ end
41
+ end
42
+
43
+ def get(accountId, url, *args)
44
+ response_body = @token.get(url(accountId, url, *args), "Accept" => "application/json").body
45
+ JSON.parse(response_body)
46
+ end
47
+
48
+ def post(accountId, url, parameters)
49
+ @token.post(url(accountId, url), parameters)
50
+ end
51
+
52
+ def delete(accountId, url, *args)
53
+ @token.delete(url(accountId, url, *args))
54
+ end
55
+
56
+ def parametrize(options)
57
+ URI.escape(
58
+ options.collect do |k, v|
59
+ v = v.to_i if k == :since
60
+ v = v.join(',') if v.instance_of?(Array)
61
+ k = k.to_s.gsub('_', '')
62
+ "#{k}=#{v}"
63
+ end.join('&'))
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,78 @@
1
+ # The exception session keeps state between twilio and context-io webhooks. Currently, the system can only handle
2
+ # one call session at a time, although we plan to change that in future versions.
3
+ exception_session = nil
4
+
5
+
6
+ # ====== CONTEXT-IO TWILIO ENDPOINTS
7
+
8
+ post '/matched_email' do
9
+ return if LastResort::Scheduler.new.get_matching_schedule.nil?
10
+
11
+ contact_names = LastResort::Scheduler.new.get_matching_schedule[:contacts]
12
+ contacts = []
13
+ contact_names.each do |name|
14
+ contacts.push(LastResort::Contact.new(name.to_s, CONFIG.contacts[name][:phone]))
15
+ end
16
+
17
+ hookData = JSON.parse(request_body.read)
18
+ exception_session = LastResort::ExceptionSession.new(contacts, hookData["message_data"]["subject"])
19
+ exception_session.notify
20
+ end
21
+
22
+
23
+ # ====== SINATRA TWILIO ENDPOINTS
24
+
25
+ # Service check method
26
+ get '/twilio' do
27
+ "twilio callbacks up and running!"
28
+ end
29
+
30
+ # Performs a test call based on the user's configuration
31
+ get '/twilio/test' do
32
+ exception_session.notify
33
+ end
34
+
35
+ # Method invoked to determine how the machine should interact with the user.
36
+ post '/twilio/call' do
37
+ content_type 'text/xml'
38
+ puts "call with #{params.inspect}"
39
+
40
+ if params[:CallStatus] == 'no-answer'
41
+ return Twilio::TwiML::Response.new { |r| r.Hangup }.text
42
+ end
43
+
44
+ response = Twilio::TwiML::Response.new do |r|
45
+ r.Say "Hello #{exception_session.callee_name}. The following error has occured: #{exception_session.description}", :voice => 'man'
46
+ r.Gather :numDigits => 1, :action => "http://#{HOST}/twilio/gather_digits" do |d|
47
+ d.Say "Please enter 1 to handle this bug that you probably didn't even create or 0 or hangup to go back to spending quality time with your family."
48
+ end
49
+ end
50
+ response.text
51
+ end
52
+
53
+ # Called when a user's call ends.
54
+ post '/twilio/status_callback' do
55
+ puts "status_callback with #{params.inspect}"
56
+ exception_session.call_next
57
+ end
58
+
59
+ # Callback to determine user input.
60
+ post '/twilio/gather_digits' do
61
+ puts "gather_digits with #{params.inspect}"
62
+
63
+ content_type 'text/xml'
64
+ digit = params[:Digits].to_i
65
+
66
+ case digit
67
+ when 1 # User handles call, so don't call anyone else
68
+ puts "User entered 1"
69
+ exception_session.end
70
+ response = Twilio::TwiML::Response.new do |r|
71
+ r.Say "Thank you for handling this exception. Goodbye.", :voice => 'man'
72
+ r.Hangup
73
+ end
74
+ return response.text
75
+ else # Hangup this call and go to the next person
76
+ return Twilio::TwiML::Response.new {|r| r.Hangup}.text
77
+ end
78
+ end
@@ -0,0 +1,73 @@
1
+ require 'active_support/core_ext'
2
+
3
+ module LastResort
4
+ class Scheduler
5
+
6
+ def initialize
7
+ @config = CONFIG
8
+ end
9
+
10
+ def get_matching_schedule
11
+ matched_schedule = @config.schedules.find do |schedule|
12
+ match?(schedule)
13
+ end
14
+
15
+ if matched_schedule.nil?
16
+ puts "No matched schedule"
17
+ nil
18
+ else
19
+ puts "Schedule found -- calling #{matched_schedule[:contacts]}"
20
+ matched_schedule
21
+ end
22
+
23
+
24
+ end
25
+
26
+ def match? schedule
27
+ match_hours?(schedule[:hours]) and match_days?(schedule[:days])
28
+ end
29
+
30
+ def match_hours? hours, time_to_match = Time.now
31
+ expanded_hours = []
32
+ hours.each do |hour|
33
+ expanded_hours += expand_if_possible(hour)
34
+ end
35
+
36
+ expanded_hours.any? do |hour|
37
+ if hour.is_a? Range
38
+ hour.include? time_to_match.hour
39
+ elsif hour.is_a? Fixnum
40
+ hour == time_to_match.hour
41
+ end
42
+ end
43
+ end
44
+
45
+ def match_days? days, time_to_match = Time.now
46
+ day_of_week = time_to_match.strftime("%A").downcase.to_sym
47
+
48
+ expanded_days = []
49
+ days.each do |day|
50
+ expanded_days += expand_if_possible(day)
51
+ end
52
+
53
+ expanded_days.any? do |day|
54
+ day == day_of_week
55
+ end
56
+ end
57
+
58
+ def expand_if_possible symbol
59
+ case symbol
60
+ when :all_hours
61
+ [0..23]
62
+ when :off_hours
63
+ [0..8, 17..23]
64
+ when :weekdays
65
+ [:monday, :tuesday, :wednesday, :thursday, :friday]
66
+ when :weekends
67
+ [:saturday, :sunday]
68
+ else
69
+ [symbol]
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,70 @@
1
+ require 'twilio-ruby'
2
+
3
+ # Sandbox code
4
+ ACCOUNT_SID = ""
5
+ AUTH_TOKEN = ""
6
+ FROM_NUMBER = ""
7
+ HOST = ""
8
+
9
+ module LastResort
10
+
11
+ class Contact
12
+ attr_accessor :name, :number
13
+
14
+ def initialize(name = "", number = "")
15
+ @name = name
16
+ @number = number
17
+ end
18
+ end
19
+
20
+ class ExceptionSession
21
+
22
+ # Array of strings representing numbers
23
+ attr_accessor :contacts, :client, :call, :index, :description, :handled
24
+
25
+ def initialize(contacts = [], description = "a general exception has occurred")
26
+ @contacts = contacts
27
+ @description = description
28
+ @client = Twilio::REST::Client.new ACCOUNT_SID, AUTH_TOKEN
29
+ @index = -1
30
+ @handled = false
31
+ end
32
+
33
+ # Return false if call was not made
34
+ def call_next
35
+ @index += 1
36
+
37
+ return false if @contacts.empty? || @index >= @contacts.size || @handled
38
+
39
+ # Make the call
40
+ @call = @client.account.calls.create(
41
+ :from => FROM_NUMBER,
42
+ :to => @contacts[@index].number,
43
+ :url => "http://#{HOST}/twilio/call",
44
+ :status_callback => "http://#{HOST}/twilio/status_callback"
45
+ )
46
+
47
+ return true
48
+ end
49
+
50
+ # Called when someone in the queue has handled this
51
+ def end
52
+ @handled = true
53
+ end
54
+
55
+ # Begin the notification cycle
56
+ def notify
57
+ self.call_next
58
+ end
59
+
60
+ # Name of the latest callee (latest call)
61
+ def callee_name
62
+ @contacts[@index].name
63
+ end
64
+
65
+ # Number of latest callee (latest call)
66
+ def callee_number
67
+ @contacts[@index].number
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,5 @@
1
+ module Last
2
+ module Resort
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ module LastResort
2
+ class WebHookCreator
3
+ def self.create_hooks
4
+ contextio = ContextIO::Connection.new(CONFIG.contextio_key, CONFIG.contextio_secret)
5
+
6
+ # Delete everything
7
+ contextio.deleteAllWebhooks CONFIG.contextio_account
8
+
9
+ # then recreate based on the configuration
10
+ CONFIG.matchers.each do |matcher|
11
+ contextio.createWebhook CONFIG.contextio_account,
12
+ :callback_url => "http://#{CONFIG.host}/matched_email",
13
+ :failure_notif_url => "http://google.ca",
14
+ :filter_subject => matcher[:subject].source,
15
+ :sync_period => "immediate"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require "sinatra"
4
+ require "awesome_print"
5
+
6
+ FILE_DIR = File.expand_path(File.dirname(__FILE__))
7
+ PROJECT_ROOT = File.expand_path("#{FILE_DIR}/..")
8
+
9
+ # BRING IN DEPENDENCIES
10
+
11
+ $LOAD_PATH << FILE_DIR
12
+ Dir["#{FILE_DIR}/last-resort/*.rb"].each do |file_path|
13
+ require file_path
14
+ end
15
+
16
+
17
+ # SINATRA BASICS
18
+
19
+ set :port, 80
20
+
21
+ get "/" do
22
+ "Last Resort server running"
23
+ end
24
+
25
+ LastResort::WebHookCreator.create_hooks
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: last-resort
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ian Ha
9
+ - Victor Mota
10
+ - Scott Hyndman
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2012-04-19 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: sinatra
18
+ requirement: &70344951605780 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: '1.3'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: *70344951605780
27
+ - !ruby/object:Gem::Dependency
28
+ name: twilio-ruby
29
+ requirement: &70344951603920 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '3.6'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: *70344951603920
38
+ - !ruby/object:Gem::Dependency
39
+ name: activesupport
40
+ requirement: &70344951602680 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '3.2'
46
+ type: :runtime
47
+ prerelease: false
48
+ version_requirements: *70344951602680
49
+ - !ruby/object:Gem::Dependency
50
+ name: thor
51
+ requirement: &70344951600840 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: '0.14'
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: *70344951600840
60
+ - !ruby/object:Gem::Dependency
61
+ name: oauth
62
+ requirement: &70344951595260 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: '0.4'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: *70344951595260
71
+ - !ruby/object:Gem::Dependency
72
+ name: gli
73
+ requirement: &70344951593480 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ version: '1.6'
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: *70344951593480
82
+ - !ruby/object:Gem::Dependency
83
+ name: awesome_print
84
+ requirement: &70344951592620 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: *70344951592620
93
+ description: Call your phone when critical emails arrive
94
+ email:
95
+ - ianha0@gmail.com
96
+ - vimota@gmail.com
97
+ - scotty.hyndman@gmail.com
98
+ executables:
99
+ - last-resort
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - .gitignore
104
+ - Gemfile
105
+ - Gemfile.lock
106
+ - LICENSE
107
+ - README.md
108
+ - Rakefile
109
+ - bin/last-resort
110
+ - config/config.rb
111
+ - last-resort.gemspec
112
+ - lib/last-resort.rb
113
+ - lib/last-resort/config-lang.rb
114
+ - lib/last-resort/contextio.rb
115
+ - lib/last-resort/controller.rb
116
+ - lib/last-resort/scheduler.rb
117
+ - lib/last-resort/twilio.rb
118
+ - lib/last-resort/version.rb
119
+ - lib/last-resort/webhooks.rb
120
+ homepage: ''
121
+ licenses: []
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ! '>='
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 1.8.10
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: Call your phone when critical emails arrive
144
+ test_files: []
145
+ has_rdoc: