controlio 0.0.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 66da730afcbcb36506a5371d5306f24635b34bce
4
+ data.tar.gz: d2318325e89ba3c363e925c0342ac170ad16932d
5
+ SHA512:
6
+ metadata.gz: 9addcaf9b61cf6d2301dea98a688410f819b7afead4de08e6e683d956830e317cfee9ffcc15e7b954282bbb588c2dcd74cd9501719fa242234eb5d750a2bef12
7
+ data.tar.gz: 8be945ea90569eb4146ba148121393eb1d6072ab501a85482b216241190c59663151d21f43605083e112f75eba8c959dd129bd4219bff197fe006ad8c316b329
data/.gitignore ADDED
@@ -0,0 +1,41 @@
1
+ # Created by https://www.gitignore.io
2
+
3
+ ### Ruby ###
4
+ *.gem
5
+ *.rbc
6
+ /.config
7
+ /coverage/
8
+ /InstalledFiles
9
+ /pkg/
10
+ /spec/reports/
11
+ /test/tmp/
12
+ /test/version_tmp/
13
+ /tmp/
14
+
15
+ ## Specific to RubyMotion:
16
+ .dat*
17
+ .repl_history
18
+ build/
19
+
20
+ ## Documentation cache and generated files:
21
+ /.yardoc/
22
+ /_yardoc/
23
+ /doc/
24
+ /rdoc/
25
+
26
+ ## Environment normalisation:
27
+ /.bundle/
28
+ /lib/bundler/man/
29
+
30
+ # for a library or gem, you might want to ignore these files since the code is
31
+ # intended to run in multiple environments; otherwise, check them in:
32
+ # Gemfile.lock
33
+ # .ruby-version
34
+ # .ruby-gemset
35
+
36
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
37
+ .rvmrc
38
+
39
+ *.pid
40
+
41
+ gems/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,36 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ controlio (0.0.3)
5
+ activesupport (~> 4.1.6)
6
+ daemons (~> 1.1.9)
7
+ rest_client (~> 1.7.3)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (4.1.6)
13
+ i18n (~> 0.6, >= 0.6.9)
14
+ json (~> 1.7, >= 1.7.7)
15
+ minitest (~> 5.1)
16
+ thread_safe (~> 0.1)
17
+ tzinfo (~> 1.1)
18
+ daemons (1.1.9)
19
+ i18n (0.6.11)
20
+ json (1.8.1)
21
+ minitest (5.4.0)
22
+ netrc (0.7.7)
23
+ rake (10.3.2)
24
+ rest_client (1.7.3)
25
+ netrc (~> 0.7.7)
26
+ thread_safe (0.3.4)
27
+ tzinfo (1.2.2)
28
+ thread_safe (~> 0.1)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bundler (~> 1.6)
35
+ controlio!
36
+ rake
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Controlio
2
+
3
+ Controlio is a simple service which allows you to control your computer via SMS **and voice**. With one simple registration step, you not longer have to be bound to your clunkly machine for on-the-go operations. No more running out of the house, only to remember you forgot to do something on your computer!
4
+
5
+ ## Setup
6
+
7
+ Install the gem with `gem install controlio`. Run the setup with `controlio-setup`. Once you're set up, you can launch the client at any point with `controlio start`, which will also automatically background it.
8
+
9
+ ## Screenshots
10
+
11
+ ![Demo 1](http://cl.ly/XsUb/IMG_1775.PNG)
12
+ ![Demo 2](http://cl.ly/XtOz/IMG_1776.PNG)
13
+ ![Demo 3](http://cl.ly/XsJy/IMG_1777.PNG)
14
+
15
+ ## Commands
16
+
17
+ Commands are located in the [commands](commands/) folder. Each command must implement `initialize(*args)`, `go`, `respond`, `media?`, and `matches`.
18
+
19
+ #### Hello World
20
+
21
+ Computer says "Hello, World!" and responds with the same text.
22
+
23
+ #### Lock
24
+
25
+ Computer locks its screen and notifies you that it has done so.
26
+
27
+ #### Open
28
+
29
+ Takes a URI as a parameter. Computer opens the URI as appropriate.
30
+
31
+ #### Play
32
+
33
+ Takes an optional name of an appliction as a parameter. Computer plays the media in the given or default application, and notifies you of the name of the currently playing media.
34
+
35
+ #### Pause
36
+
37
+ Takes an optional name of an appliction as a parameter. Computer pauses the media in the given or default application.
38
+
39
+ #### Say
40
+
41
+ Takes a sentence as a parameter. Computer says the given sentence.
42
+
43
+ #### Type
44
+
45
+ Takes a sentence as a parameter. Computer types out the given sentence.
46
+
47
+ #### Screenshot
48
+
49
+ Computer takes a screenshot and returns it to you.
50
+
51
+ #### Picture
52
+
53
+ Computer takes a picture with the built-in webcam and returns it to you.
54
+
55
+ #### Set Volume
56
+
57
+ Takes an integer between 0 and 100 as a parameter. Computer sets its volume to the interger provided.
58
+
59
+ #### Cat
60
+
61
+ ????
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/controlio ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'daemons'
4
+ require 'controlio'
5
+
6
+ Daemons.run_proc('controlio') do
7
+ Controlio.start
8
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'controlio'
4
+
5
+ Controlio.start
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'controlio'
4
+
5
+ Controlio.setup
data/controlio.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'controlio/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "controlio"
8
+ spec.version = Controlio::VERSION
9
+ spec.authors = ["Yasyf Mohamedali"]
10
+ spec.email = ["yasyfm@gmail.com"]
11
+ spec.summary = "Control your computer through SMS! "
12
+ spec.homepage = "https://github.com/yasyf/controlio"
13
+ spec.required_ruby_version = ">= 2.0"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.6"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_runtime_dependency 'activesupport', '~> 4.1.6'
23
+ spec.add_runtime_dependency 'daemons', '~> 1.1.9'
24
+ spec.add_runtime_dependency 'rest_client', '~> 1.7.3'
25
+ end
@@ -0,0 +1,22 @@
1
+ module Commands
2
+ class Cat
3
+ def initialize(args, settings)
4
+ end
5
+
6
+ def go
7
+ `open 'http://www.youtube.com/watch?v=QH2-TGUlwu4'`
8
+ end
9
+
10
+ def respond
11
+ "Nyan Nyan Nyan Nyan Nyan Nyan Nyan..."
12
+ end
13
+
14
+ def media?
15
+ false
16
+ end
17
+
18
+ def self.matches
19
+ ['cat', 'cat mit', 'nyan']
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Commands
2
+ class HelloWorld
3
+ def initialize(args, settings)
4
+ end
5
+
6
+ def go
7
+ `say "Hello, World!"`
8
+ end
9
+
10
+ def respond
11
+ "Hello, World!"
12
+ end
13
+
14
+ def media?
15
+ false
16
+ end
17
+
18
+ def self.matches
19
+ ['hw', 'hello world']
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Commands
2
+ class Lock
3
+ def initialize(args, settings)
4
+ end
5
+
6
+ def go
7
+ `/System/Library/CoreServices/Menu\\ Extras/User.menu/Contents/Resources/CGSession -suspend`
8
+ end
9
+
10
+ def respond
11
+ "Your screen has been locked!"
12
+ end
13
+
14
+ def media?
15
+ false
16
+ end
17
+
18
+ def self.matches
19
+ ['lock', 'lock my computer', 'lock my screen']
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ module Commands
2
+ class Open
3
+ def initialize(args, settings)
4
+ @url = args.join(' ').strip
5
+ end
6
+
7
+ def go
8
+ `open '#{@url}'`
9
+ end
10
+
11
+ def respond
12
+ ""
13
+ end
14
+
15
+ def media?
16
+ false
17
+ end
18
+
19
+ def self.matches
20
+ ['open']
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Commands
2
+ class Pause
3
+ def initialize(args, settings)
4
+ @app = args.first || settings.get('default_player')
5
+ end
6
+
7
+ def go
8
+ `osascript -e 'tell application "#{@app}" to pause'`
9
+ end
10
+
11
+ def respond
12
+ ""
13
+ end
14
+
15
+ def media?
16
+ false
17
+ end
18
+
19
+ def self.matches
20
+ ['pause', 'pause my music']
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ require 'rest_client'
2
+
3
+ module Commands
4
+ class Picture
5
+ FILE_LOCATION = "/tmp/tmp.jpg"
6
+
7
+ def initialize(args, settings)
8
+ @api_root = settings.get('api_root')
9
+ end
10
+
11
+ def go
12
+ `imagesnap -q #{FILE_LOCATION}`
13
+ response = RestClient.post "#{@api_root}/upload", file: File.new(FILE_LOCATION, 'rb')
14
+ @url = JSON.load(response)['url']
15
+ end
16
+
17
+ def respond
18
+ @url
19
+ end
20
+
21
+ def media?
22
+ true
23
+ end
24
+
25
+ def self.matches
26
+ ['picture', 'snap']
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ module Commands
2
+ class Play
3
+ def initialize(args, settings)
4
+ @app = args.first || settings.get('default_player')
5
+ end
6
+
7
+ def go
8
+ `osascript -e 'tell application "#{@app}" to launch'`
9
+ sleep 1
10
+ `osascript -e 'tell application "#{@app}" to play'`
11
+ end
12
+
13
+ def respond
14
+ "Now playing: #{`osascript -e 'tell application "#{@app}" to get the name of the current track'`}".chomp rescue ""
15
+ end
16
+
17
+ def media?
18
+ false
19
+ end
20
+
21
+ def self.matches
22
+ ['play', 'play my music']
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ module Commands
2
+ class Say
3
+ def initialize(args, settings)
4
+ @text = args.join(' ')
5
+ end
6
+
7
+ def go
8
+ `say #{@text}`
9
+ end
10
+
11
+ def respond
12
+ nil
13
+ end
14
+
15
+ def media?
16
+ false
17
+ end
18
+
19
+ def self.matches
20
+ ['say']
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ require 'rest_client'
2
+
3
+ module Commands
4
+ class Screenshot
5
+ FILE_LOCATION = "/tmp/tmp.jpg"
6
+
7
+ def initialize(args, settings)
8
+ @api_root = settings.get('api_root')
9
+ end
10
+
11
+ def go
12
+ `screencapture -t jpg -x #{FILE_LOCATION}`
13
+ response = RestClient.post "#{@api_root}/upload", file: File.new(FILE_LOCATION, 'rb')
14
+ @url = JSON.load(response)['url']
15
+ end
16
+
17
+ def respond
18
+ @url
19
+ end
20
+
21
+ def media?
22
+ true
23
+ end
24
+
25
+ def self.matches
26
+ ['screenshot']
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ module Commands
2
+ class SetVolume
3
+ def initialize(args, settings)
4
+ @level = args.first
5
+ end
6
+
7
+ def go
8
+ `osascript -e 'set volume output volume #{@level}'`
9
+ end
10
+
11
+ def respond
12
+ "Volume is now #{@level}"
13
+ end
14
+
15
+ def media?
16
+ false
17
+ end
18
+
19
+ def self.matches
20
+ ['set volume']
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Commands
2
+ class Skip
3
+ def initialize(args, settings)
4
+ @app = args.first || settings.get('default_player')
5
+ end
6
+
7
+ def go
8
+ `osascript -e 'tell application "#{@app}" to next track'`
9
+ end
10
+
11
+ def respond
12
+ "Now playing: #{`osascript -e 'tell application "#{@app}" to get the name of the current track'`}".chomp rescue ""
13
+ end
14
+
15
+ def media?
16
+ false
17
+ end
18
+
19
+ def self.matches
20
+ ['skip', 'skip this song', 'next', 'next song']
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Commands
2
+ class Type
3
+ def initialize(args, settings)
4
+ @keys = args.join(' ')
5
+ end
6
+
7
+ def go
8
+ `osascript -e 'tell application "System Events" to keystroke "#{@keys}"'`
9
+ end
10
+
11
+ def respond
12
+ "Your string has been typed!"
13
+ end
14
+
15
+ def media?
16
+ false
17
+ end
18
+
19
+ def self.matches
20
+ ['type']
21
+ end
22
+ end
23
+ end
data/lib/controlio.rb ADDED
@@ -0,0 +1,18 @@
1
+ require "controlio/client"
2
+ require "controlio/settings"
3
+
4
+ module Controlio
5
+ def self.start
6
+ client = Client.new
7
+ client.go
8
+ end
9
+
10
+ def self.setup
11
+ begin
12
+ File.delete File.expand_path("~/.controlio.json")
13
+ rescue
14
+ end
15
+ settings = Settings.new
16
+ exit
17
+ end
18
+ end
@@ -0,0 +1,87 @@
1
+ require 'json'
2
+ require 'rest_client'
3
+ require 'active_support/all'
4
+ require "controlio/settings"
5
+
6
+ module Controlio
7
+ class Client
8
+
9
+ def initialize
10
+ authorize
11
+ generate_command_mappings!
12
+ end
13
+
14
+ def go
15
+ loop do
16
+ check_for_commands.each do |c|
17
+ klass, args = command_options(c)
18
+ if klass.present?
19
+ puts "Running #{klass} (#{args})"
20
+ instance = klass.new(args, @settings)
21
+ instance.go
22
+ send instance.respond, instance.media?
23
+ else
24
+ send "Command '#{c['message']}' was not found!"
25
+ end
26
+ destroy c['_id']
27
+ end
28
+ sleep 1
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def authorize
35
+ @settings = Settings.new
36
+ @api_key = @settings.get('api_key')
37
+ @api_root = @settings.get('api_root')
38
+ end
39
+
40
+ def send(message, media=false)
41
+ if media
42
+ options = {key: @api_key, media_url: message}
43
+ else
44
+ options = {key: @api_key, message: message}
45
+ end
46
+ RestClient.post("#{@api_root}/send", options) if message.present?
47
+ end
48
+
49
+ def destroy(id)
50
+ RestClient.post "#{@api_root}/destroy/#{id}", key: @api_key
51
+ end
52
+
53
+ def check_for_commands
54
+ begin
55
+ JSON.load(RestClient.get("#{@api_root}/poll?key=#{@api_key}"))['commands']
56
+ rescue
57
+ []
58
+ end
59
+ end
60
+
61
+ def command_options(c)
62
+ options = c['message'].downcase.split(' ')
63
+ (1..options.length).each do |i|
64
+ command = options.first(i).join(' ')
65
+ return [@mappings[command], options[i..-1]] if @mappings.include? command
66
+ end
67
+ []
68
+ end
69
+
70
+ def prefixes
71
+ ['']
72
+ end
73
+
74
+ def generate_command_mappings!
75
+ @mappings = {}
76
+ Dir[File.expand_path("#{File.dirname(__FILE__)}/../commands/*.rb")].each do |file|
77
+ require_relative file
78
+ klass = "Commands::#{file.split("/").last.split('.').first.camelize}".constantize
79
+ klass.matches.each do |match|
80
+ prefixes.each do |p|
81
+ @mappings["#{p}#{match}"] = klass
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,48 @@
1
+ require 'json'
2
+ require 'rest_client'
3
+ require 'io/console'
4
+
5
+ module Controlio
6
+ class Settings
7
+ API_ROOT = ENV['API_ROOT'] || 'http://ym-remote-control-web.herokuapp.com'
8
+ SETTINGS_FILE = File.expand_path "~/.controlio.json"
9
+
10
+ def initialize
11
+ @settings = (begin JSON.parse File.read(SETTINGS_FILE) rescue create_new_settings end).with_indifferent_access
12
+ end
13
+
14
+ def get(key)
15
+ @settings[key]
16
+ end
17
+
18
+ def set(key, value)
19
+ @settings[key] = value
20
+ File.write SETTINGS_FILE, settings.to_json
21
+ end
22
+
23
+ private
24
+
25
+ def create_new_settings
26
+ settings = {api_root: API_ROOT}
27
+ puts "Enter your phone number"
28
+ number = gets.chomp
29
+ if JSON.load(RestClient.get("#{API_ROOT}/check/#{number}"))['registered']
30
+ key = nil
31
+ while !key
32
+ puts "Enter your password"
33
+ password = STDIN.noecho(&:gets).chomp
34
+ key = JSON.load(RestClient.get("#{API_ROOT}/key/#{number}?password=#{password}"))['key']
35
+ end
36
+ else
37
+ puts "Pick a password"
38
+ password = gets.chomp
39
+ key = RestClient.post JSON.load(RestClient.post("#{API_ROOT}/key/#{number}", password: password))['key']
40
+ end
41
+ puts "Successfuly authenticated!"
42
+ settings[:api_key] = key
43
+ File.write SETTINGS_FILE, settings.to_json
44
+ settings
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module Controlio
2
+ VERSION = "0.0.3"
3
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: controlio
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Yasyf Mohamedali
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 4.1.6
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 4.1.6
55
+ - !ruby/object:Gem::Dependency
56
+ name: daemons
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.1.9
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.1.9
69
+ - !ruby/object:Gem::Dependency
70
+ name: rest_client
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.7.3
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.7.3
83
+ description:
84
+ email:
85
+ - yasyfm@gmail.com
86
+ executables:
87
+ - controlio
88
+ - controlio-client
89
+ - controlio-setup
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - ".gitignore"
94
+ - Gemfile
95
+ - Gemfile.lock
96
+ - README.md
97
+ - Rakefile
98
+ - bin/controlio
99
+ - bin/controlio-client
100
+ - bin/controlio-setup
101
+ - controlio.gemspec
102
+ - lib/commands/cat.rb
103
+ - lib/commands/hello_world.rb
104
+ - lib/commands/lock.rb
105
+ - lib/commands/open.rb
106
+ - lib/commands/pause.rb
107
+ - lib/commands/picture.rb
108
+ - lib/commands/play.rb
109
+ - lib/commands/say.rb
110
+ - lib/commands/screenshot.rb
111
+ - lib/commands/set_volume.rb
112
+ - lib/commands/skip.rb
113
+ - lib/commands/type.rb
114
+ - lib/controlio.rb
115
+ - lib/controlio/client.rb
116
+ - lib/controlio/settings.rb
117
+ - lib/controlio/version.rb
118
+ homepage: https://github.com/yasyf/controlio
119
+ licenses: []
120
+ metadata: {}
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '2.0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 2.2.2
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Control your computer through SMS!
141
+ test_files: []