last-resort 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: