musica 0.1.2 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +37 -22
- data/README.md +46 -6
- data/exe/musica +5 -1
- data/lib/musica/cli.rb +11 -26
- data/lib/musica/config.rb +29 -0
- data/lib/musica/intent.json +47 -0
- data/lib/musica/interpreter.rb +126 -0
- data/lib/musica/player.rb +8 -3
- data/lib/musica/version.rb +1 -1
- data/lib/musica.rb +2 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b7def5733a1fb7f5b9d36a812d3cbf6e615d88ec49f34bdf4eb6bbcf3127f613
|
|
4
|
+
data.tar.gz: 1d07b3cf2461f9e5e91fe29b72272c49b53c06ce2d3e6ec0c56ce2663566d713
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1341f4bf9097a0aba074697a3f9335fe36e73b113ef2fb841920f2b3bcca0ed711084437451ea68ac0b9f20b78c2d764b4d3916bb0cef149bbcdf359d10c4b42
|
|
7
|
+
data.tar.gz: 2688cf16ee45c9f0f676eea0f938b0c232968e1178b45a2459e88633f564fd239036d35ea7d7befe60451f56a224921007f72b82507b91647fcd72f80feacb44
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
## [0.2.0] - 2026-10-19
|
|
2
|
+
|
|
3
|
+
- Add natural language commands with `-n`, using Apple's on-device model through `fm`
|
|
4
|
+
- Play tracks and albums from the Music library by name, artist or album
|
|
5
|
+
|
|
1
6
|
## [0.1.2] - 2025-09-01
|
|
2
7
|
|
|
3
8
|
- Add YAML configuration support for player selection
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
musica (0.
|
|
4
|
+
musica (0.2.0)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
@@ -14,17 +14,20 @@ GEM
|
|
|
14
14
|
io-console (0.6.0)
|
|
15
15
|
irb (1.6.3)
|
|
16
16
|
reline (>= 0.3.0)
|
|
17
|
-
json (
|
|
17
|
+
json (3.1.0)
|
|
18
18
|
language_server-protocol (3.17.0.3)
|
|
19
|
+
lint_roller (1.1.0)
|
|
19
20
|
parallel (1.22.1)
|
|
20
|
-
parser (3.
|
|
21
|
+
parser (3.3.12.0)
|
|
21
22
|
ast (~> 2.4.1)
|
|
23
|
+
racc
|
|
24
|
+
prism (1.9.0)
|
|
25
|
+
racc (1.8.1)
|
|
22
26
|
rainbow (3.1.1)
|
|
23
|
-
rake (13.
|
|
24
|
-
regexp_parser (2.
|
|
27
|
+
rake (13.4.2)
|
|
28
|
+
regexp_parser (2.13.1)
|
|
25
29
|
reline (0.3.3)
|
|
26
30
|
io-console (~> 0.5)
|
|
27
|
-
rexml (3.2.5)
|
|
28
31
|
rspec (3.12.0)
|
|
29
32
|
rspec-core (~> 3.12.0)
|
|
30
33
|
rspec-expectations (~> 3.12.0)
|
|
@@ -38,31 +41,43 @@ GEM
|
|
|
38
41
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
39
42
|
rspec-support (~> 3.12.0)
|
|
40
43
|
rspec-support (3.12.0)
|
|
41
|
-
rubocop (1.
|
|
42
|
-
json (
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
rubocop (1.91.0)
|
|
45
|
+
json (>= 2.3)
|
|
46
|
+
language_server-protocol (~> 3.17.0.2)
|
|
47
|
+
lint_roller (~> 1.1.0)
|
|
48
|
+
parallel (>= 1.10)
|
|
49
|
+
parser (>= 3.3.0.2)
|
|
45
50
|
rainbow (>= 2.2.2, < 4.0)
|
|
46
|
-
regexp_parser (>=
|
|
47
|
-
|
|
48
|
-
rubocop-ast (>= 1.26.0, < 2.0)
|
|
51
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
52
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
49
53
|
ruby-progressbar (~> 1.7)
|
|
50
|
-
unicode-display_width (>= 2.4.0, <
|
|
51
|
-
rubocop-ast (1.
|
|
52
|
-
parser (>= 3.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
55
|
+
rubocop-ast (1.50.0)
|
|
56
|
+
parser (>= 3.3.7.2)
|
|
57
|
+
prism (~> 1.7)
|
|
58
|
+
rubocop-performance (1.26.1)
|
|
59
|
+
lint_roller (~> 1.1)
|
|
60
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
61
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
56
62
|
ruby-progressbar (1.13.0)
|
|
57
|
-
standard (1.
|
|
63
|
+
standard (1.57.0)
|
|
58
64
|
language_server-protocol (~> 3.17.0.2)
|
|
59
|
-
|
|
60
|
-
rubocop
|
|
65
|
+
lint_roller (~> 1.0)
|
|
66
|
+
rubocop (~> 1.91.0)
|
|
67
|
+
standard-custom (~> 1.0.0)
|
|
68
|
+
standard-performance (~> 1.8)
|
|
69
|
+
standard-custom (1.0.2)
|
|
70
|
+
lint_roller (~> 1.0)
|
|
71
|
+
rubocop (~> 1.50)
|
|
72
|
+
standard-performance (1.9.0)
|
|
73
|
+
lint_roller (~> 1.1)
|
|
74
|
+
rubocop-performance (~> 1.26.0)
|
|
61
75
|
unicode-display_width (2.4.2)
|
|
62
76
|
|
|
63
77
|
PLATFORMS
|
|
64
78
|
arm64-darwin-22
|
|
65
79
|
arm64-darwin-25
|
|
80
|
+
arm64-darwin-27
|
|
66
81
|
x86_64-linux
|
|
67
82
|
|
|
68
83
|
DEPENDENCIES
|
data/README.md
CHANGED
|
@@ -3,11 +3,6 @@
|
|
|
3
3
|
This is a Ruby command-line tool that interacts with music players on macOS
|
|
4
4
|
using AppleScript.
|
|
5
5
|
|
|
6
|
-
Welcome to your new gem! In this directory, you'll find the files you need to be
|
|
7
|
-
able to package up your Ruby library into a gem. Put your Ruby code in the file
|
|
8
|
-
`lib/musica`. To experiment with that code, run `bin/console` for an interactive
|
|
9
|
-
prompt.
|
|
10
|
-
|
|
11
6
|
## Installation
|
|
12
7
|
|
|
13
8
|
Install the gem and add to the application's Gemfile by executing:
|
|
@@ -20,11 +15,56 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
|
20
15
|
|
|
21
16
|
## Usage
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
Musica passes its arguments straight to your music player as an AppleScript
|
|
19
|
+
command, so `musica <command>` runs:
|
|
20
|
+
|
|
21
|
+
```applescript
|
|
22
|
+
tell application "<player>" to <command>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Any command in the player's AppleScript dictionary works (open it in Script
|
|
26
|
+
Editor with File > Open Dictionary). For example:
|
|
24
27
|
|
|
25
28
|
$ musica play
|
|
26
29
|
$ musica pause
|
|
30
|
+
$ musica playpause
|
|
27
31
|
$ musica next track
|
|
32
|
+
$ musica previous track
|
|
33
|
+
$ musica set sound volume to 50
|
|
34
|
+
|
|
35
|
+
After each command, Musica prints the current status: the artist and track
|
|
36
|
+
while playing, otherwise the player state:
|
|
37
|
+
|
|
38
|
+
$ musica play
|
|
39
|
+
🎧 Daft Punk - Digital Love
|
|
40
|
+
$ musica pause
|
|
41
|
+
🎧 paused
|
|
42
|
+
|
|
43
|
+
Run `musica` without arguments to print the status alone.
|
|
44
|
+
|
|
45
|
+
Commands can differ between players. For example, Music uses
|
|
46
|
+
`set shuffle enabled to true` and Spotify uses `set shuffling to true`. Wrap
|
|
47
|
+
commands that contain quotes in single quotes so the shell keeps them:
|
|
48
|
+
|
|
49
|
+
$ musica 'play playlist "Favorite Songs"'
|
|
50
|
+
|
|
51
|
+
### Natural language
|
|
52
|
+
|
|
53
|
+
With `-n`, Musica uses Apple's on-device model, through the `fm` command, to
|
|
54
|
+
turn a plain request into a command. This needs macOS 26 with Apple
|
|
55
|
+
Intelligence.
|
|
56
|
+
|
|
57
|
+
$ musica -n skip this song
|
|
58
|
+
$ musica -n turn it down a bit
|
|
59
|
+
$ musica -n play Digital Love by Daft Punk
|
|
60
|
+
$ musica -n play something by Queen
|
|
61
|
+
$ musica -n play the album Adore
|
|
62
|
+
|
|
63
|
+
It understands play, pause, next and previous track, volume changes, and
|
|
64
|
+
playing a track or album from your library by name, artist or album. To play
|
|
65
|
+
an album in order, Musica fills a playlist named "Musica" with its tracks and
|
|
66
|
+
plays that playlist, replacing what was in it before. Playing from your library
|
|
67
|
+
only works with Music.
|
|
28
68
|
|
|
29
69
|
## Configuration
|
|
30
70
|
|
data/exe/musica
CHANGED
data/lib/musica/cli.rb
CHANGED
|
@@ -1,41 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "yaml"
|
|
4
|
-
require "fileutils"
|
|
5
|
-
|
|
6
3
|
module Musica
|
|
7
4
|
class Cli
|
|
8
|
-
def initialize(argv)
|
|
9
|
-
@
|
|
10
|
-
@
|
|
5
|
+
def initialize(argv, player: Player.new(application: Config.player), interpreter: Interpreter.new, out: $stdout)
|
|
6
|
+
@natural = argv.first == "-n"
|
|
7
|
+
@prompt = argv.drop(@natural ? 1 : 0).join(" ")
|
|
8
|
+
@player = player
|
|
9
|
+
@interpreter = interpreter
|
|
10
|
+
@out = out
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def run
|
|
14
|
-
player.tell
|
|
15
|
-
|
|
14
|
+
player.tell command unless prompt.empty?
|
|
15
|
+
out << "\u{1F3A7} #{player.status}\n"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
private
|
|
19
19
|
|
|
20
|
-
attr_reader :
|
|
21
|
-
|
|
22
|
-
def load_config
|
|
23
|
-
config_dir = "#{ENV["XDG_CONFIG_HOME"]}/musica" || File.expand_path("~/.config/musica")
|
|
24
|
-
config_file = File.join(config_dir, "config.yml")
|
|
25
|
-
|
|
26
|
-
unless File.exist?(config_file)
|
|
27
|
-
create_config(config_dir, config_file)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
YAML.load_file(config_file).dig("player")
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def create_config(config_dir, config_file)
|
|
34
|
-
FileUtils.mkdir_p(config_dir)
|
|
35
|
-
|
|
36
|
-
default_config = { "player" => "Music" }
|
|
20
|
+
attr_reader :interpreter, :natural, :out, :player, :prompt
|
|
37
21
|
|
|
38
|
-
|
|
22
|
+
def command
|
|
23
|
+
natural ? interpreter.call(prompt) : prompt
|
|
39
24
|
end
|
|
40
25
|
end
|
|
41
26
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Musica
|
|
7
|
+
module Config
|
|
8
|
+
DEFAULTS = {"player" => "Music"}.freeze
|
|
9
|
+
|
|
10
|
+
def self.player(env: ENV)
|
|
11
|
+
file = path(env)
|
|
12
|
+
|
|
13
|
+
unless File.exist?(file)
|
|
14
|
+
FileUtils.mkdir_p(File.dirname(file))
|
|
15
|
+
File.write(file, YAML.dump(DEFAULTS))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
(YAML.load_file(file) || {}).fetch("player", DEFAULTS["player"])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.path(env)
|
|
22
|
+
base = env["XDG_CONFIG_HOME"]
|
|
23
|
+
base = File.join(env.fetch("HOME"), ".config") if base.nil? || base.empty?
|
|
24
|
+
|
|
25
|
+
File.join(base, "musica", "config.yml")
|
|
26
|
+
end
|
|
27
|
+
private_class_method :path
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Intent",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"required": [
|
|
6
|
+
"action"
|
|
7
|
+
],
|
|
8
|
+
"properties": {
|
|
9
|
+
"action": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"enum": [
|
|
12
|
+
"play",
|
|
13
|
+
"pause",
|
|
14
|
+
"next_track",
|
|
15
|
+
"previous_track",
|
|
16
|
+
"change_volume",
|
|
17
|
+
"play_track",
|
|
18
|
+
"play_album",
|
|
19
|
+
"unknown"
|
|
20
|
+
],
|
|
21
|
+
"description": "What to do"
|
|
22
|
+
},
|
|
23
|
+
"name": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Track name, for play_track"
|
|
26
|
+
},
|
|
27
|
+
"artist": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Artist name, for play_track or play_album"
|
|
30
|
+
},
|
|
31
|
+
"album": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Album name, for play_album"
|
|
34
|
+
},
|
|
35
|
+
"delta": {
|
|
36
|
+
"type": "integer",
|
|
37
|
+
"description": "Volume change from -100 to 100, for change_volume"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"x-order": [
|
|
41
|
+
"action",
|
|
42
|
+
"name",
|
|
43
|
+
"artist",
|
|
44
|
+
"album",
|
|
45
|
+
"delta"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "open3"
|
|
5
|
+
|
|
6
|
+
module Musica
|
|
7
|
+
class Interpreter
|
|
8
|
+
SCHEMA = File.expand_path("intent.json", __dir__)
|
|
9
|
+
|
|
10
|
+
INSTRUCTIONS = <<~TEXT
|
|
11
|
+
You turn a request to a music player into an action.
|
|
12
|
+
- play: resume playback
|
|
13
|
+
- pause: pause or stop playback
|
|
14
|
+
- next_track: skip to the next song ("next", "skip")
|
|
15
|
+
- previous_track: go back to the previous song
|
|
16
|
+
- change_volume: louder or quieter; delta is the change, 10 for a bit, 30 for a lot, negative for quieter
|
|
17
|
+
- play_track: play a specific song; name is the song title, artist only if the request names one
|
|
18
|
+
- play_album: play an album; album is the album title, artist only if the request names one
|
|
19
|
+
- unknown: anything else
|
|
20
|
+
TEXT
|
|
21
|
+
|
|
22
|
+
FM = lambda do |request|
|
|
23
|
+
output, status = Open3.capture2(
|
|
24
|
+
"fm", "respond", "--no-stream", "--greedy",
|
|
25
|
+
"--schema", SCHEMA, "--instructions", INSTRUCTIONS, request
|
|
26
|
+
)
|
|
27
|
+
raise Error, "fm failed to interpret \"#{request}\"" unless status.success?
|
|
28
|
+
|
|
29
|
+
output
|
|
30
|
+
rescue Errno::ENOENT
|
|
31
|
+
raise Error, "Natural language commands need fm (macOS 26 with Apple Intelligence)"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
COMMANDS = {
|
|
35
|
+
"play" => "play",
|
|
36
|
+
"pause" => "pause",
|
|
37
|
+
"next_track" => "next track",
|
|
38
|
+
"previous_track" => "previous track"
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
# Music can't play an album or queue tracks, so refill a playlist with the
|
|
42
|
+
# album in disc and track order and play that instead
|
|
43
|
+
ALBUM = <<~APPLESCRIPT
|
|
44
|
+
set found to every track of library playlist 1 whose %<conditions>s
|
|
45
|
+
if found is {} then error "No album " & %<album>s & " in your library"
|
|
46
|
+
set positions to {}
|
|
47
|
+
repeat with t in found
|
|
48
|
+
set end of positions to (disc number of t) * 1000 + (track number of t)
|
|
49
|
+
end repeat
|
|
50
|
+
if not (exists user playlist "Musica") then make new user playlist with properties {name:"Musica"}
|
|
51
|
+
set musica to user playlist "Musica"
|
|
52
|
+
delete every track of musica
|
|
53
|
+
repeat (count of found) times
|
|
54
|
+
set best to 0
|
|
55
|
+
repeat with i from 1 to count of found
|
|
56
|
+
if item i of positions is not missing value then
|
|
57
|
+
if best is 0 or item i of positions < item best of positions then set best to i
|
|
58
|
+
end if
|
|
59
|
+
end repeat
|
|
60
|
+
duplicate item best of found to musica
|
|
61
|
+
set item best of positions to missing value
|
|
62
|
+
end repeat
|
|
63
|
+
play musica
|
|
64
|
+
APPLESCRIPT
|
|
65
|
+
|
|
66
|
+
def initialize(runner: FM)
|
|
67
|
+
@runner = runner
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def call(request)
|
|
71
|
+
intent = JSON.parse(runner.call(request))
|
|
72
|
+
name = mentioned(intent["name"], request)
|
|
73
|
+
artist = mentioned(intent["artist"], request)
|
|
74
|
+
album = mentioned(intent["album"], request)
|
|
75
|
+
|
|
76
|
+
case intent["action"]
|
|
77
|
+
when "play", "play_track", "play_album"
|
|
78
|
+
if album && !name
|
|
79
|
+
play_album(album, artist)
|
|
80
|
+
elsif name || artist
|
|
81
|
+
play_track(name, artist, album)
|
|
82
|
+
else
|
|
83
|
+
"play"
|
|
84
|
+
end
|
|
85
|
+
when "change_volume"
|
|
86
|
+
volume(intent["delta"], request)
|
|
87
|
+
else
|
|
88
|
+
COMMANDS.fetch(intent["action"]) { not_understood(request) }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
attr_reader :runner
|
|
95
|
+
|
|
96
|
+
def mentioned(value, request)
|
|
97
|
+
value if value.is_a?(String) && !value.empty? && request.downcase.include?(value.downcase)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def play_track(name, artist, album)
|
|
101
|
+
"play (some track of library playlist 1 whose #{conditions("name" => name, "artist" => artist, "album" => album)})"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def play_album(album, artist)
|
|
105
|
+
format(ALBUM, conditions: conditions("album" => album, "artist" => artist), album: quote(album))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def conditions(filters)
|
|
109
|
+
filters.compact.map { |key, value| "#{key} is #{quote(value)}" }.join(" and ")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def volume(delta, request)
|
|
113
|
+
not_understood(request) unless delta.is_a?(Integer)
|
|
114
|
+
|
|
115
|
+
"set sound volume to (sound volume + #{delta})"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def quote(value)
|
|
119
|
+
%("#{value.gsub(/[\\"]/) { |char| "\\#{char}" }}")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def not_understood(request)
|
|
123
|
+
raise Error, "Couldn't understand \"#{request}\""
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
data/lib/musica/player.rb
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
3
5
|
module Musica
|
|
4
6
|
class Player
|
|
5
|
-
|
|
7
|
+
OSASCRIPT = ->(script) { Open3.capture2("osascript", "-e", script).first.chomp }
|
|
8
|
+
|
|
9
|
+
def initialize(application:, runner: OSASCRIPT)
|
|
6
10
|
@application = application
|
|
11
|
+
@runner = runner
|
|
7
12
|
end
|
|
8
13
|
|
|
9
14
|
def status
|
|
@@ -11,12 +16,12 @@ module Musica
|
|
|
11
16
|
end
|
|
12
17
|
|
|
13
18
|
def tell(command)
|
|
14
|
-
|
|
19
|
+
runner.call(%(tell application "#{application}"\n#{command}\nend tell))
|
|
15
20
|
end
|
|
16
21
|
|
|
17
22
|
private
|
|
18
23
|
|
|
19
|
-
attr_reader :application
|
|
24
|
+
attr_reader :application, :runner
|
|
20
25
|
|
|
21
26
|
def artist
|
|
22
27
|
tell "return artist of the current track"
|
data/lib/musica/version.rb
CHANGED
data/lib/musica.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: musica
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ariel Rzezak
|
|
@@ -28,6 +28,9 @@ files:
|
|
|
28
28
|
- exe/musica
|
|
29
29
|
- lib/musica.rb
|
|
30
30
|
- lib/musica/cli.rb
|
|
31
|
+
- lib/musica/config.rb
|
|
32
|
+
- lib/musica/intent.json
|
|
33
|
+
- lib/musica/interpreter.rb
|
|
31
34
|
- lib/musica/player.rb
|
|
32
35
|
- lib/musica/version.rb
|
|
33
36
|
- sig/musica.rbs
|
|
@@ -52,7 +55,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
52
55
|
- !ruby/object:Gem::Version
|
|
53
56
|
version: '0'
|
|
54
57
|
requirements: []
|
|
55
|
-
rubygems_version:
|
|
58
|
+
rubygems_version: 4.0.17
|
|
56
59
|
specification_version: 4
|
|
57
60
|
summary: Control macOS music players from the command-line
|
|
58
61
|
test_files: []
|